Overview

The Stacknet SDK (@stacknet/sdk) is the recommended way to interact with the Geoff API. It provides type-safe functions for text generation, streaming, structured output, tool calling, embeddings, image/speech/video generation, and React UI hooks.
This is the recommended way to use Geoff. The Stacknet SDK provides the richest feature set with built-in streaming, structured output, tool calling, and React integrations.

Installation

npm install @stacknet/sdk

Setup

Create a provider instance pointing to the Geoff API:
import { createStackNetProvider } from "@stacknet/sdk";

const geoff = createStackNetProvider({
  baseURL: "https://geoff.ai/api/v1",
  name: "geoff",
  apiKey: process.env.GEOFF_API_KEY,
});
The provider gives you access to different model types:
// Chat / language model (default)
const model = geoff("magma");

// Explicit model accessors
const chat = geoff.chatModel("magma");
const completion = geoff.completionModel("magma");
const embedding = geoff.embeddingModel("magma");
const image = geoff.imageModel("magma");

Text Generation

generateText

Generate text with a single call. Supports tools, multi-turn conversations, and structured output.
import { generateText } from "@stacknet/sdk";

const { text, usage, finishReason } = await generateText({
  model: geoff("magma"),
  prompt: "Explain quantum computing in simple terms.",
  maxOutputTokens: 1024,
  temperature: 0.7,
});

console.log(text);

Multi-turn conversation

const result = await generateText({
  model: geoff("magma"),
  system: "You are a helpful coding assistant.",
  messages: [
    { role: "user", content: "Write a Python function to reverse a string." },
    { role: "assistant", content: "def reverse(s): return s[::-1]" },
    { role: "user", content: "Now make it handle Unicode correctly." },
  ],
});

Parameters

ParameterTypeDescription
modelLanguageModelThe model to use
promptstringSimple text prompt
messagesModelMessage[]Multi-turn conversation messages
systemstringSystem prompt
maxOutputTokensnumberMax tokens to generate
temperaturenumberSampling temperature (0-2)
topPnumberNucleus sampling
topKnumberTop-K sampling
stopSequencesstring[]Stop generation on these strings
seednumberFor deterministic output
maxRetriesnumberRetry failed requests (default: 2)
timeoutnumberRequest timeout in ms
toolsToolSetAvailable tools for the model
toolChoiceToolChoiceHow to select tools

streamText

Stream text generation in real-time. Returns an async iterable.
import { streamText } from "@stacknet/sdk";

const result = streamText({
  model: geoff("magma"),
  prompt: "Write a short story about AI.",
});

for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

// Or get the full result after streaming
const finalText = await result.text;
const usage = await result.usage;

Pipe to HTTP response (Next.js / Node.js)

// Next.js App Router
export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: geoff("magma"),
    messages,
  });

  return result.toDataStreamResponse();
}
// Node.js / Express
app.post("/api/chat", async (req, res) => {
  const result = streamText({
    model: geoff("magma"),
    prompt: req.body.prompt,
  });

  await result.pipeToResponse(res);
});

Structured Output

generateObject

Generate type-safe structured data using a schema.
import { generateObject } from "@stacknet/sdk";
import { z } from "zod";

const { object } = await generateObject({
  model: geoff("magma"),
  schema: z.object({
    name: z.string(),
    age: z.number(),
    interests: z.array(z.string()),
  }),
  prompt: "Generate a fictional character profile.",
});

console.log(object.name); // Type-safe access

streamObject

Stream structured output as it’s being generated.
import { streamObject } from "@stacknet/sdk";

const result = streamObject({
  model: geoff("magma"),
  schema: z.object({
    title: z.string(),
    chapters: z.array(z.object({
      heading: z.string(),
      summary: z.string(),
    })),
  }),
  prompt: "Create a book outline about machine learning.",
});

for await (const partialObject of result.partialObjectStream) {
  console.log(partialObject);
}

Tool Calling

Define tools the model can invoke during generation.
import { generateText, tool } from "@stacknet/sdk";
import { z } from "zod";

const result = await generateText({
  model: geoff("magma"),
  prompt: "Find all critical vulnerabilities in our infrastructure and create tickets for them",
  tools: {
    scanInfrastructure: tool({
      description: "Run a security scan against cloud infrastructure and return findings",
      inputSchema: z.object({
        target: z.enum(["aws", "gcp", "azure", "all"]).describe("Cloud provider to scan"),
        severity: z.enum(["critical", "high", "medium", "all"]).describe("Minimum severity level"),
      }),
      execute: async ({ target, severity }) => {
        const findings = await securityScanner.scan({ provider: target, minSeverity: severity });
        return findings; // [{ id, title, severity, resource, remediation }]
      },
    }),

    createTicket: tool({
      description: "Create a ticket in the issue tracker for a security finding",
      inputSchema: z.object({
        title: z.string(),
        description: z.string(),
        priority: z.enum(["p0", "p1", "p2"]),
        assignee: z.string().optional(),
      }),
      execute: async ({ title, description, priority, assignee }) => {
        const ticket = await issueTracker.create({ title, description, priority, assignee });
        return { ticketId: ticket.id, url: ticket.url };
      },
    }),

    notifyTeam: tool({
      description: "Send a Slack notification to the security team channel",
      inputSchema: z.object({
        channel: z.string().describe("Slack channel name"),
        message: z.string(),
      }),
      execute: async ({ channel, message }) => {
        await slack.postMessage({ channel, text: message });
        return { sent: true, channel };
      },
    }),
  },
  maxSteps: 10, // Allow multi-step tool use
});

console.log(result.text);       // Summary of what was done
console.log(result.toolCalls);  // All tool invocations
console.log(result.toolResults); // Results from each tool

Tool with approval

const dangerousTool = tool({
  description: "Delete a file",
  inputSchema: z.object({ path: z.string() }),
  needsApproval: true,
  execute: async ({ path }) => {
    // Only runs after approval
    return { deleted: path };
  },
});

Embeddings

import { embed, embedMany } from "@stacknet/sdk";

// Single embedding
const { embedding } = await embed({
  model: geoff.embeddingModel("magma"),
  value: "The quick brown fox",
});

// Batch embeddings
const { embeddings } = await embedMany({
  model: geoff.embeddingModel("magma"),
  values: ["Hello world", "Goodbye world"],
});

Image Generation

import { generateImage } from "@stacknet/sdk";

const { image } = await generateImage({
  model: geoff.imageModel("magma"),
  prompt: "A futuristic city skyline at dusk",
  size: "1024x1024",
});

Speech Generation

import { generateSpeech } from "@stacknet/sdk";

const { audio } = await generateSpeech({
  model: geoff("magma"),
  text: "Hello, welcome to Geoff AI.",
  voice: "alloy",
});

Transcription

import { transcribe } from "@stacknet/sdk";

const { transcript } = await transcribe({
  model: geoff("magma"),
  audio: audioBuffer, // Uint8Array | ArrayBuffer
  language: "en",
});

React Hooks

useChat

Build chat UIs with the useChat hook.
import { useChat } from "@stacknet/sdk/react";

export function ChatComponent() {
  const { messages, sendMessage, status, error } = useChat({
    id: "my-chat",
    onFinish: ({ message }) => console.log("Done:", message),
    onError: (error) => console.error(error),
  });

  const handleSend = (text: string) => {
    sendMessage([{
      role: "user",
      parts: [{ type: "text", text }],
    }]);
  };

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          {msg.parts.map((part, i) =>
            part.type === "text" ? <p key={i}>{part.text}</p> : null
          )}
        </div>
      ))}
      {status === "streaming" && <p>Thinking...</p>}
    </div>
  );
}

useChat return values

PropertyTypeDescription
messagesUIMessage[]All messages in the conversation
statusChatStatus"idle", "streaming", "error"
errorError | undefinedCurrent error if any
sendMessagefunctionSend new messages
regeneratefunctionRegenerate the last response
stopfunctionStop streaming
resumeStreamfunctionResume an interrupted stream
setMessagesfunctionManually set messages
clearErrorfunctionClear current error

Provider Configuration

Full configuration options for createStackNetProvider:
const geoff = createStackNetProvider({
  // Required
  baseURL: "https://geoff.ai/api/v1",
  name: "geoff",

  // Authentication
  apiKey: process.env.GEOFF_API_KEY,

  // Custom headers & query params
  headers: { "X-Custom-Header": "value" },
  queryParams: { "version": "2" },

  // Features
  supportsStructuredOutputs: true,
  includeUsage: true,

  // Custom fetch implementation
  fetch: customFetch,

  // Transform request body before sending
  transformRequestBody: (body) => ({
    ...body,
    custom_field: "value",
  }),
});

Error Handling

The SDK provides typed error classes for common failure modes:
import { APICallError, InvalidPromptError } from "@stacknet/sdk";

try {
  const result = await generateText({
    model: geoff("magma"),
    prompt: "Hello",
  });
} catch (error) {
  if (error instanceof APICallError) {
    console.error("API error:", error.message, error.statusCode);
  } else if (error instanceof InvalidPromptError) {
    console.error("Bad prompt:", error.message);
  }
}

Timeout & Retry

const result = await generateText({
  model: geoff("magma"),
  prompt: "Hello",
  maxRetries: 3,
  timeout: {
    totalMs: 30000,   // 30s total
    chunkMs: 5000,    // 5s per chunk (streaming)
    stepMs: 10000,    // 10s per step (tool loops)
  },
  abortSignal: AbortSignal.timeout(60000),
});