@mantyx/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/sse.ts","../src/tools.ts","../src/zod-to-json-schema.ts","../src/client.ts","../src/version.ts"],"sourcesContent":["/**\n * Error types raised by the MANTYX SDK.\n */\n\nexport class MantyxError extends Error {\n readonly code: string;\n readonly status: number | undefined;\n readonly hint: string | undefined;\n\n constructor(\n message: string,\n opts: { code?: string; status?: number; hint?: string } = {},\n ) {\n super(message);\n this.name = \"MantyxError\";\n this.code = opts.code ?? \"mantyx_error\";\n this.status = opts.status;\n this.hint = opts.hint;\n }\n}\n\nexport class MantyxNetworkError extends MantyxError {\n constructor(message: string, opts: { cause?: unknown } = {}) {\n super(message, { code: \"network\" });\n this.name = \"MantyxNetworkError\";\n if (opts.cause !== undefined) {\n (this as Error & { cause?: unknown }).cause = opts.cause;\n }\n }\n}\n\nexport class MantyxAuthError extends MantyxError {\n constructor(message = \"Invalid or missing API key\") {\n super(message, { code: \"unauthorized\", status: 401 });\n this.name = \"MantyxAuthError\";\n }\n}\n\nexport class MantyxToolError extends MantyxError {\n readonly toolName: string;\n\n constructor(toolName: string, message: string) {\n super(`Local tool ${JSON.stringify(toolName)} failed: ${message}`, {\n code: \"local_tool_failed\",\n });\n this.name = \"MantyxToolError\";\n this.toolName = toolName;\n }\n}\n\nexport class MantyxRunError extends MantyxError {\n readonly runId: string;\n readonly subtype: string;\n\n constructor(runId: string, subtype: string, message: string) {\n super(message, { code: subtype });\n this.name = \"MantyxRunError\";\n this.runId = runId;\n this.subtype = subtype;\n }\n}\n","/**\n * Minimal Server-Sent Events parser.\n *\n * Reads a `ReadableStream<Uint8Array>` (the body of a `fetch()` response) and\n * yields parsed events with `id`, `event` and `data` fields. We deliberately\n * keep this dependency-free instead of pulling in `eventsource` / `eventsource-parser`\n * so the SDK has the smallest possible install footprint.\n *\n * Reconnect/replay is handled at a higher layer using `Last-Event-ID` (the\n * default for browsers' `EventSource`) plus a `?lastSeq=` query param so curl\n * users and SSE-via-fetch consumers both work.\n */\n\nexport interface SseEvent {\n id?: string;\n event?: string;\n data: string;\n}\n\nexport interface SseStreamOptions {\n /** AbortSignal for cancellation. */\n signal?: AbortSignal;\n}\n\n/**\n * Async generator yielding parsed SSE events from a fetch response body.\n * Comment frames (`:keep-alive`) are dropped.\n */\nexport async function* readSseStream(\n body: ReadableStream<Uint8Array> | null,\n opts: SseStreamOptions = {},\n): AsyncGenerator<SseEvent, void, void> {\n if (!body) return;\n const reader = body.getReader();\n const decoder = new TextDecoder(\"utf-8\");\n let buffer = \"\";\n\n let cancelled = false;\n const onAbort = (): void => {\n cancelled = true;\n try {\n void reader.cancel();\n } catch {\n // ignore\n }\n };\n if (opts.signal) {\n if (opts.signal.aborted) {\n onAbort();\n } else {\n opts.signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n }\n\n try {\n while (!cancelled) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n // SSE events are separated by a blank line (\\n\\n). Some servers emit \\r\\n.\n let sepIdx: number;\n while ((sepIdx = findSeparator(buffer)) !== -1) {\n const raw = buffer.slice(0, sepIdx);\n buffer = buffer.slice(sepIdx + (buffer.startsWith(\"\\r\", sepIdx) ? 4 : 2));\n const ev = parseEventBlock(raw);\n if (ev) yield ev;\n }\n }\n } finally {\n if (opts.signal) opts.signal.removeEventListener(\"abort\", onAbort);\n try {\n reader.releaseLock();\n } catch {\n // ignore\n }\n }\n}\n\nfunction findSeparator(s: string): number {\n const lf = s.indexOf(\"\\n\\n\");\n const crlf = s.indexOf(\"\\r\\n\\r\\n\");\n if (lf === -1) return crlf;\n if (crlf === -1) return lf;\n return Math.min(lf, crlf);\n}\n\nfunction parseEventBlock(block: string): SseEvent | null {\n const lines = block.split(/\\r?\\n/);\n let id: string | undefined;\n let event: string | undefined;\n const dataLines: string[] = [];\n for (const line of lines) {\n if (line.length === 0) continue;\n if (line.startsWith(\":\")) continue; // comment / heartbeat\n const colonIdx = line.indexOf(\":\");\n const field = colonIdx === -1 ? line : line.slice(0, colonIdx);\n let value = colonIdx === -1 ? \"\" : line.slice(colonIdx + 1);\n if (value.startsWith(\" \")) value = value.slice(1);\n if (field === \"id\") id = value;\n else if (field === \"event\") event = value;\n else if (field === \"data\") dataLines.push(value);\n }\n if (dataLines.length === 0 && id === undefined && event === undefined) {\n return null;\n }\n return {\n ...(id !== undefined ? { id } : {}),\n ...(event !== undefined ? { event } : {}),\n data: dataLines.join(\"\\n\"),\n };\n}\n","/**\n * Public tool helpers for the MANTYX SDK.\n *\n * defineLocalTool({ name, description, parameters, execute })\n * → A tool that runs in the developer's process. The MANTYX server pauses\n * the agent loop, emits a `local_tool_call` event, and waits for the SDK\n * to POST the result back.\n *\n * mantyxTool(id) → Reference an existing workspace `Tool` row by id.\n * mantyxPluginTool(name) → Reference a built-in plugin tool by `@plugin/tool` name.\n */\nimport type { z } from \"zod\";\n\nexport type ZodLikeObject = z.ZodType<Record<string, unknown>> & {\n _def?: unknown;\n parse?: (value: unknown) => unknown;\n};\n\nexport interface LocalTool<TArgs = Record<string, unknown>> {\n readonly kind: \"local\";\n readonly name: string;\n readonly description: string;\n readonly parameters: ZodLikeObject | undefined;\n readonly execute: (args: TArgs) => Promise<string> | string;\n}\n\nexport interface MantyxToolRef {\n readonly kind: \"mantyx\";\n readonly id: string;\n}\n\nexport interface MantyxPluginToolRef {\n readonly kind: \"mantyx_plugin\";\n readonly name: string;\n}\n\nexport type ToolRef = MantyxToolRef | MantyxPluginToolRef | LocalTool;\n\nexport interface DefineLocalToolOptions<T extends ZodLikeObject | undefined> {\n /** Lowercase alphanumeric + underscore, max 64 chars. */\n name: string;\n description?: string;\n parameters?: T;\n execute: (args: T extends ZodLikeObject ? z.infer<T> : Record<string, unknown>) => Promise<string> | string;\n}\n\nexport function defineLocalTool<T extends ZodLikeObject | undefined>(\n opts: DefineLocalToolOptions<T>,\n): LocalTool {\n if (!/^[a-zA-Z0-9_]{1,64}$/.test(opts.name)) {\n throw new Error(\n `Invalid local tool name ${JSON.stringify(opts.name)}: must match /^[a-zA-Z0-9_]{1,64}$/`,\n );\n }\n return {\n kind: \"local\",\n name: opts.name,\n description: opts.description ?? \"\",\n parameters: opts.parameters,\n execute: opts.execute as LocalTool[\"execute\"],\n };\n}\n\nexport function mantyxTool(id: string): MantyxToolRef {\n if (typeof id !== \"string\" || id.length === 0) {\n throw new Error(\"mantyxTool(id): id must be a non-empty string\");\n }\n return { kind: \"mantyx\", id };\n}\n\nexport function mantyxPluginTool(name: string): MantyxPluginToolRef {\n if (typeof name !== \"string\" || !name.startsWith(\"@\") || !name.includes(\"/\")) {\n throw new Error(\n `mantyxPluginTool(name): expected \"@plugin-slug/tool-name\", got ${JSON.stringify(name)}`,\n );\n }\n return { kind: \"mantyx_plugin\", name };\n}\n\nexport function isLocalTool(t: ToolRef): t is LocalTool {\n return t.kind === \"local\";\n}\n","/**\n * Lightweight Zod → JSON Schema converter for tool parameter definitions.\n *\n * Tries `z.toJSONSchema` (Zod v4+) first; falls back to a hand-rolled walker\n * for v3 schemas so the SDK works on a wide range of zod versions.\n *\n * The output is a JSON-Schema-shaped object with `type: \"object\"`, `properties`,\n * and `required`. The MANTYX server feeds this to LLM providers verbatim, so\n * unsupported zod features (effects, transforms, intersections) degrade to a\n * permissive `\"object\"` description rather than failing.\n */\nimport { z } from \"zod\";\n\ntype JsonSchema = Record<string, unknown>;\n\ninterface ZodLikeWithToJsonSchema {\n toJSONSchema?: (schema: unknown) => JsonSchema;\n}\n\nexport function zodToJsonSchema(schema: z.ZodType<unknown>): JsonSchema {\n const builtIn = (z as unknown as ZodLikeWithToJsonSchema).toJSONSchema;\n if (typeof builtIn === \"function\") {\n try {\n const out = builtIn.call(z, schema) as JsonSchema;\n if (out && typeof out === \"object\") return out;\n } catch {\n // fall through to manual converter\n }\n }\n return convertNode(schema);\n}\n\nfunction convertNode(schema: z.ZodType<unknown>): JsonSchema {\n const def = (schema as unknown as { _def?: { typeName?: string } })._def;\n const typeName = def?.typeName;\n switch (typeName) {\n case \"ZodString\":\n return { type: \"string\" };\n case \"ZodNumber\":\n return { type: \"number\" };\n case \"ZodBoolean\":\n return { type: \"boolean\" };\n case \"ZodNull\":\n return { type: \"null\" };\n case \"ZodLiteral\": {\n const value = (def as { value?: unknown }).value;\n return { const: value, type: typeof value };\n }\n case \"ZodEnum\": {\n const values = (def as { values?: readonly string[] }).values ?? [];\n return { type: \"string\", enum: [...values] };\n }\n case \"ZodArray\": {\n const inner = (def as { type?: z.ZodType<unknown> }).type;\n return {\n type: \"array\",\n items: inner ? convertNode(inner) : {},\n };\n }\n case \"ZodOptional\":\n case \"ZodNullable\": {\n const inner = (def as { innerType?: z.ZodType<unknown> }).innerType;\n return inner ? convertNode(inner) : {};\n }\n case \"ZodDefault\": {\n const inner = (def as { innerType?: z.ZodType<unknown> }).innerType;\n return inner ? convertNode(inner) : {};\n }\n case \"ZodObject\": {\n const shape = (def as { shape?: () => Record<string, z.ZodType<unknown>> }).shape;\n const fields = typeof shape === \"function\" ? shape() : (shape as Record<string, z.ZodType<unknown>> | undefined);\n const properties: Record<string, JsonSchema> = {};\n const required: string[] = [];\n if (fields) {\n for (const [key, value] of Object.entries(fields)) {\n properties[key] = convertNode(value);\n const innerDef = (value as unknown as { _def?: { typeName?: string } })._def;\n const innerTypeName = innerDef?.typeName;\n if (innerTypeName !== \"ZodOptional\" && innerTypeName !== \"ZodDefault\") {\n required.push(key);\n }\n }\n }\n const out: JsonSchema = { type: \"object\", properties };\n if (required.length > 0) out.required = required;\n return out;\n }\n default:\n return {};\n }\n}\n\n/**\n * Coerce a JSON-Schema-shaped value into a wire object suitable for the\n * MANTYX local-tool definition payload. Accepts either a Zod schema or an\n * already-shaped JSON Schema object.\n */\nexport function toToolParametersWire(\n parameters: z.ZodType<unknown> | JsonSchema | undefined,\n): JsonSchema {\n if (!parameters) return { type: \"object\", properties: {} };\n if (typeof (parameters as { _def?: unknown })._def !== \"undefined\") {\n return zodToJsonSchema(parameters as z.ZodType<unknown>);\n }\n return parameters as JsonSchema;\n}\n","/**\n * MANTYX SDK client: HTTP plumbing, model catalog, run + session drivers.\n */\nimport {\n MantyxAuthError,\n MantyxError,\n MantyxNetworkError,\n MantyxRunError,\n MantyxToolError,\n} from \"./errors.js\";\nimport { readSseStream } from \"./sse.js\";\nimport type { LocalTool, ToolRef } from \"./tools.js\";\nimport { isLocalTool } from \"./tools.js\";\nimport { toToolParametersWire } from \"./zod-to-json-schema.js\";\n\nexport const DEFAULT_BASE_URL = \"https://api.mantyx.com\";\n\nexport interface MantyxClientOptions {\n apiKey: string;\n workspaceSlug: string;\n /** Defaults to `https://api.mantyx.com`. Override for self-hosted instances. */\n baseUrl?: string;\n /** Optional `fetch` override (e.g. node-fetch wrapper, or a custom HTTP client). */\n fetch?: typeof fetch;\n /** Default per-request timeout in milliseconds. Default: 60s. */\n timeoutMs?: number;\n}\n\nexport interface ModelInfo {\n id: string;\n label: string;\n provider: string;\n vendorModelId: string;\n source: \"workspace_provider\" | \"platform_offering\";\n contextWindowTokens: number | null;\n pricing: {\n inputPer1MUsd: number | null;\n outputPer1MUsd: number | null;\n cacheReadPer1MUsd: number | null;\n } | null;\n}\n\nexport interface ModelCatalog {\n models: ModelInfo[];\n defaultModelId: string | null;\n}\n\nexport interface AgentSpecBase {\n name?: string;\n /**\n * Reference to a persisted MANTYX agent in this workspace. When set, the\n * server hydrates `systemPrompt`, `modelId`, and the agent's own tools\n * (memory, skills, plugin tools, …) from the Agent row at run time, and any\n * `tools` you supply here are merged on top — typically `local` tools the\n * SDK wants the agent to be able to call back into.\n *\n * Either `agentId` or `systemPrompt` must be set.\n */\n agentId?: string;\n /** Required unless `agentId` is set. */\n systemPrompt?: string;\n modelId?: string;\n tools?: ToolRef[];\n budgets?: { maxToolTurns?: number };\n /**\n * Flat string→string KV carried alongside the run / session for\n * observability. Use it to tag runs with your own application identifiers\n * (customer id, environment, workflow name, …) — the values are visible in\n * the MANTYX dashboard and can be filtered there.\n *\n * Limits enforced server-side: max 16 entries; keys match\n * `[A-Za-z0-9._-]{1,64}`; values are strings ≤ 256 chars; serialized JSON\n * ≤ 4 KB. For session-scoped runs, the session's metadata is inherited and\n * any per-message override is merged on top.\n */\n metadata?: Record<string, string>;\n}\n\nexport interface RunSpec extends AgentSpecBase {\n prompt?: string;\n messages?: Array<{ role: \"user\" | \"assistant\" | \"system\"; content: string }>;\n /** Receives streaming assistant text deltas. */\n onAssistantDelta?: (delta: string) => void;\n /** Receives raw events (assistant_message, local_tool_call, tool_result, ...) for advanced consumers. */\n onEvent?: (event: RunEvent) => void;\n /** Aborts the run on the client and best-effort cancels server-side. */\n signal?: AbortSignal;\n}\n\nexport type SessionSpec = AgentSpecBase;\n\nexport interface RunResult {\n runId: string;\n text: string;\n events: RunEvent[];\n}\n\nexport interface RunEventBase {\n seq: number;\n type: string;\n}\n\nexport interface AssistantDeltaEvent extends RunEventBase {\n type: \"assistant_delta\";\n text: string;\n}\n\nexport interface ThinkingDeltaEvent extends RunEventBase {\n type: \"thinking_delta\";\n text: string;\n}\n\nexport interface AssistantMessageEvent extends RunEventBase {\n type: \"assistant_message\";\n text: string;\n}\n\nexport interface ServerToolResultEvent extends RunEventBase {\n type: \"tool_result\";\n name: string;\n args?: Record<string, unknown>;\n ok?: boolean;\n summary?: string;\n phase?: \"start\" | \"end\";\n}\n\nexport interface LocalToolCallEvent extends RunEventBase {\n type: \"local_tool_call\";\n toolUseId: string;\n name: string;\n args: Record<string, unknown>;\n}\n\nexport interface LocalToolResultInEvent extends RunEventBase {\n type: \"local_tool_result_in\";\n toolUseId: string;\n result?: string;\n error?: string;\n}\n\nexport interface ResultEvent extends RunEventBase {\n type: \"result\";\n subtype: string;\n text?: string;\n error?: string;\n}\n\nexport interface ErrorEvent extends RunEventBase {\n type: \"error\";\n error: string;\n code?: string;\n}\n\nexport interface CancelledEvent extends RunEventBase {\n type: \"cancelled\";\n reason?: string;\n}\n\nexport type RunEvent =\n | AssistantDeltaEvent\n | ThinkingDeltaEvent\n | AssistantMessageEvent\n | ServerToolResultEvent\n | LocalToolCallEvent\n | LocalToolResultInEvent\n | ResultEvent\n | ErrorEvent\n | CancelledEvent\n | (RunEventBase & { type: string; [key: string]: unknown });\n\nexport interface SessionInfo {\n id: string;\n name: string;\n status: \"active\" | \"ended\";\n createdAt: string;\n lastUsedAt: string;\n endedAt: string | null;\n agentSpec: AgentSpecBase;\n messages: Array<{ role: \"user\" | \"assistant\" | \"system\"; content: string }>;\n /** Metadata that was attached to the session at create time, returned for observability. */\n metadata: Record<string, string>;\n}\n\nexport class MantyxClient {\n readonly options: Required<Pick<MantyxClientOptions, \"apiKey\" | \"workspaceSlug\" | \"baseUrl\">> & {\n fetch: typeof fetch;\n timeoutMs: number;\n };\n\n constructor(opts: MantyxClientOptions) {\n if (!opts.apiKey || typeof opts.apiKey !== \"string\") {\n throw new MantyxError(\"apiKey is required\");\n }\n if (!opts.workspaceSlug || typeof opts.workspaceSlug !== \"string\") {\n throw new MantyxError(\"workspaceSlug is required\");\n }\n const f = opts.fetch ?? globalThis.fetch;\n if (typeof f !== \"function\") {\n throw new MantyxError(\n \"Global fetch is not available; pass a custom `fetch` implementation in MantyxClientOptions.\",\n );\n }\n this.options = {\n apiKey: opts.apiKey,\n workspaceSlug: opts.workspaceSlug,\n baseUrl: (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\"),\n fetch: f,\n timeoutMs: opts.timeoutMs ?? 60_000,\n };\n }\n\n // -------------------------------------------------------------- Models\n\n async listModels(): Promise<ModelCatalog> {\n return this.request<ModelCatalog>({\n method: \"GET\",\n path: \"/models\",\n });\n }\n\n // ------------------------------------------------------------- One-shot\n\n async runAgent(spec: RunSpec): Promise<RunResult> {\n const handlers = collectLocalHandlers(spec.tools ?? []);\n const created = await this.request<{ runId: string; streamUrl: string }>({\n method: \"POST\",\n path: \"/agent-runs\",\n body: serializeAgentSpec(spec, {\n prompt: spec.prompt,\n messages: spec.messages,\n }),\n });\n return this.driveRun(created.runId, handlers, {\n ...(spec.onAssistantDelta ? { onAssistantDelta: spec.onAssistantDelta } : {}),\n ...(spec.onEvent ? { onEvent: spec.onEvent } : {}),\n ...(spec.signal ? { signal: spec.signal } : {}),\n });\n }\n\n async *streamAgent(spec: RunSpec): AsyncGenerator<RunEvent, void, void> {\n const handlers = collectLocalHandlers(spec.tools ?? []);\n const created = await this.request<{ runId: string; streamUrl: string }>({\n method: \"POST\",\n path: \"/agent-runs\",\n body: serializeAgentSpec(spec, {\n prompt: spec.prompt,\n messages: spec.messages,\n }),\n });\n yield* this.streamRunEvents(created.runId, handlers, spec.signal);\n }\n\n // ------------------------------------------------------------- Sessions\n\n async createSession(spec: SessionSpec): Promise<AgentSession> {\n const handlers = collectLocalHandlers(spec.tools ?? []);\n const created = await this.request<{ sessionId: string; name: string; createdAt: string }>({\n method: \"POST\",\n path: \"/agent-sessions\",\n body: serializeAgentSpec(spec),\n });\n return new AgentSession(this, created.sessionId, handlers);\n }\n\n async resumeSession(\n sessionId: string,\n opts: { tools?: ToolRef[] } = {},\n ): Promise<AgentSession> {\n // Verify the session exists and is still active. Optionally refresh tool defs.\n await this.getSessionInfo(sessionId);\n const handlers = collectLocalHandlers(opts.tools ?? []);\n return new AgentSession(this, sessionId, handlers, opts.tools);\n }\n\n async endSession(sessionId: string): Promise<void> {\n await this.request<{ ok: boolean }>({\n method: \"DELETE\",\n path: `/agent-sessions/${encodeURIComponent(sessionId)}`,\n });\n }\n\n async getSessionInfo(sessionId: string): Promise<SessionInfo> {\n return this.request<SessionInfo>({\n method: \"GET\",\n path: `/agent-sessions/${encodeURIComponent(sessionId)}`,\n });\n }\n\n // ----------------------------------------------------------- Internals\n\n /** Drive an existing run to completion (collect events, dispatch local tools). */\n async driveRun(\n runId: string,\n handlers: Map<string, LocalTool>,\n opts: {\n onAssistantDelta?: (delta: string) => void;\n onEvent?: (event: RunEvent) => void;\n signal?: AbortSignal;\n } = {},\n ): Promise<RunResult> {\n const collected: RunEvent[] = [];\n let finalText = \"\";\n for await (const ev of this.streamRunEvents(runId, handlers, opts.signal)) {\n collected.push(ev);\n if (opts.onEvent) opts.onEvent(ev);\n if (ev.type === \"assistant_delta\" && opts.onAssistantDelta) {\n opts.onAssistantDelta((ev as AssistantDeltaEvent).text);\n }\n if (ev.type === \"result\") {\n const r = ev as ResultEvent;\n if (r.subtype === \"success\") {\n finalText = typeof r.text === \"string\" ? r.text : \"\";\n } else {\n throw new MantyxRunError(runId, r.subtype, r.error ?? r.subtype);\n }\n } else if (ev.type === \"error\") {\n const e = ev as ErrorEvent;\n throw new MantyxRunError(runId, e.code ?? \"error\", e.error);\n } else if (ev.type === \"cancelled\") {\n throw new MantyxRunError(runId, \"cancelled\", \"Run was cancelled\");\n }\n }\n return { runId, text: finalText, events: collected };\n }\n\n async *streamRunEvents(\n runId: string,\n handlers: Map<string, LocalTool>,\n signal?: AbortSignal,\n ): AsyncGenerator<RunEvent, void, void> {\n const url = this.absoluteUrl(`/agent-runs/${encodeURIComponent(runId)}/stream`);\n let lastSeq = 0;\n while (true) {\n const reqUrl = lastSeq > 0 ? `${url}?lastSeq=${lastSeq}` : url;\n const res = await this.options.fetch(reqUrl, {\n method: \"GET\",\n headers: {\n ...this.authHeaders(),\n Accept: \"text/event-stream\",\n ...(lastSeq > 0 ? { \"Last-Event-ID\": String(lastSeq) } : {}),\n },\n ...(signal ? { signal } : {}),\n }).catch((err: unknown) => {\n throw new MantyxNetworkError(`Failed to open SSE stream: ${(err as Error).message}`, {\n cause: err,\n });\n });\n if (!res.ok) {\n throw await this.errorFromResponse(res);\n }\n let terminal = false;\n try {\n for await (const sseEvent of readSseStream(res.body, { ...(signal ? { signal } : {}) })) {\n let data: Record<string, unknown> = {};\n try {\n data = JSON.parse(sseEvent.data || \"{}\") as Record<string, unknown>;\n } catch {\n data = {};\n }\n const evType = sseEvent.event ?? (data.type as string | undefined) ?? \"message\";\n const seq = typeof data.seq === \"number\" ? data.seq : lastSeq;\n if (typeof seq === \"number\" && seq > lastSeq) lastSeq = seq;\n const ev = { seq, type: evType, ...data } as RunEvent;\n yield ev;\n if (evType === \"local_tool_call\") {\n const localEv = ev as LocalToolCallEvent;\n void this.dispatchLocalTool(runId, localEv, handlers).catch((err) => {\n // best-effort logging; the run will surface a `result/error` if the\n // server eventually times out.\n console.error(\"[mantyx-sdk] local tool dispatch failed:\", err);\n });\n }\n if (evType === \"result\" || evType === \"error\" || evType === \"cancelled\") {\n terminal = true;\n return;\n }\n }\n } catch (err) {\n if (signal?.aborted) {\n throw new MantyxRunError(runId, \"cancelled\", \"Run was cancelled by the client\");\n }\n // Network blip — retry after a tiny backoff with `?lastSeq=`.\n await sleep(500);\n continue;\n }\n if (terminal) return;\n // Stream closed without a terminal event (server restart, etc.) — reconnect.\n }\n }\n\n async dispatchLocalTool(\n runId: string,\n ev: LocalToolCallEvent,\n handlers: Map<string, LocalTool>,\n ): Promise<void> {\n const handler = handlers.get(ev.name);\n if (!handler) {\n await this.postToolResult(runId, ev.toolUseId, {\n error: `No local handler registered for tool ${JSON.stringify(ev.name)}`,\n });\n return;\n }\n try {\n const args = handler.parameters ? handler.parameters.parse?.(ev.args) ?? ev.args : ev.args;\n const out = await handler.execute(args as Record<string, unknown>);\n const resultText = typeof out === \"string\" ? out : JSON.stringify(out);\n await this.postToolResult(runId, ev.toolUseId, { result: resultText });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n await this.postToolResult(runId, ev.toolUseId, {\n error: new MantyxToolError(handler.name, message).message,\n });\n }\n }\n\n async postToolResult(\n runId: string,\n toolUseId: string,\n payload: { result?: string; error?: string },\n ): Promise<void> {\n await this.request<{ ok: boolean }>({\n method: \"POST\",\n path: `/agent-runs/${encodeURIComponent(runId)}/tool-results`,\n body: { toolUseId, ...payload },\n });\n }\n\n async cancelRun(runId: string): Promise<void> {\n await this.request<{ ok: boolean }>({\n method: \"POST\",\n path: `/agent-runs/${encodeURIComponent(runId)}/cancel`,\n });\n }\n\n // -------------------------------------------------------------- HTTP\n\n private absoluteUrl(path: string): string {\n return `${this.options.baseUrl}/api/v1/workspaces/${encodeURIComponent(this.options.workspaceSlug)}${path}`;\n }\n\n private authHeaders(): Record<string, string> {\n return { Authorization: `Bearer ${this.options.apiKey}` };\n }\n\n async request<T>(args: {\n method: string;\n path: string;\n body?: unknown;\n timeoutMs?: number;\n }): Promise<T> {\n const url = this.absoluteUrl(args.path);\n const ctrl = new AbortController();\n const t = setTimeout(() => ctrl.abort(), args.timeoutMs ?? this.options.timeoutMs);\n try {\n const res = await this.options.fetch(url, {\n method: args.method,\n headers: {\n ...this.authHeaders(),\n ...(args.body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n Accept: \"application/json\",\n },\n ...(args.body !== undefined ? { body: JSON.stringify(args.body) } : {}),\n signal: ctrl.signal,\n }).catch((err: unknown) => {\n if (ctrl.signal.aborted) {\n throw new MantyxNetworkError(`Request timed out after ${args.timeoutMs ?? this.options.timeoutMs}ms`);\n }\n throw new MantyxNetworkError(`Network error: ${(err as Error).message}`, { cause: err });\n });\n if (!res.ok) {\n throw await this.errorFromResponse(res);\n }\n const text = await res.text();\n if (!text) return undefined as unknown as T;\n try {\n return JSON.parse(text) as T;\n } catch (err) {\n throw new MantyxError(`Failed to parse JSON response: ${(err as Error).message}`);\n }\n } finally {\n clearTimeout(t);\n }\n }\n\n private async errorFromResponse(res: Response): Promise<MantyxError> {\n let body: { error?: string; code?: string; hint?: string } = {};\n try {\n body = (await res.json()) as typeof body;\n } catch {\n // ignore\n }\n if (res.status === 401) {\n return new MantyxAuthError(body.error ?? \"Invalid API key\");\n }\n return new MantyxError(body.error ?? `HTTP ${res.status}`, {\n code: body.code ?? `http_${res.status}`,\n status: res.status,\n ...(body.hint ? { hint: body.hint } : {}),\n });\n }\n}\n\n// ---------------------------------------------------------------- Sessions\n\nexport class AgentSession {\n readonly id: string;\n readonly client: MantyxClient;\n private readonly handlers: Map<string, LocalTool>;\n private readonly toolsForResume: ToolRef[] | undefined;\n\n constructor(\n client: MantyxClient,\n id: string,\n handlers: Map<string, LocalTool>,\n toolsForResume?: ToolRef[],\n ) {\n this.client = client;\n this.id = id;\n this.handlers = handlers;\n this.toolsForResume = toolsForResume;\n }\n\n async send(\n prompt: string,\n opts: {\n onAssistantDelta?: (s: string) => void;\n signal?: AbortSignal;\n /**\n * Per-message metadata override. Server-side this is merged on top of\n * the session's metadata at run-creation time (run-level keys win).\n * Useful for tagging individual turns (e.g. `{ \"trace_id\": \"abc\" }`).\n */\n metadata?: Record<string, string>;\n } = {},\n ): Promise<RunResult> {\n const created = await this.client.request<{ runId: string; streamUrl: string }>({\n method: \"POST\",\n path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,\n body: {\n prompt,\n ...(this.toolsForResume ? { tools: serializeToolRefs(this.toolsForResume) } : {}),\n ...(opts.metadata && Object.keys(opts.metadata).length > 0\n ? { metadata: opts.metadata }\n : {}),\n },\n });\n return this.client.driveRun(created.runId, this.handlers, {\n ...(opts.onAssistantDelta ? { onAssistantDelta: opts.onAssistantDelta } : {}),\n ...(opts.signal ? { signal: opts.signal } : {}),\n });\n }\n\n async *stream(\n prompt: string,\n opts: { signal?: AbortSignal; metadata?: Record<string, string> } = {},\n ): AsyncGenerator<RunEvent, void, void> {\n const created = await this.client.request<{ runId: string; streamUrl: string }>({\n method: \"POST\",\n path: `/agent-sessions/${encodeURIComponent(this.id)}/messages`,\n body: {\n prompt,\n ...(this.toolsForResume ? { tools: serializeToolRefs(this.toolsForResume) } : {}),\n ...(opts.metadata && Object.keys(opts.metadata).length > 0\n ? { metadata: opts.metadata }\n : {}),\n },\n });\n yield* this.client.streamRunEvents(created.runId, this.handlers, opts.signal);\n }\n\n async history(): Promise<Array<{ role: \"user\" | \"assistant\" | \"system\"; content: string }>> {\n const info = await this.client.getSessionInfo(this.id);\n return info.messages;\n }\n\n async info(): Promise<SessionInfo> {\n return this.client.getSessionInfo(this.id);\n }\n\n async end(): Promise<void> {\n await this.client.endSession(this.id);\n }\n}\n\n// ---------------------------------------------------------------- Helpers\n\nfunction serializeAgentSpec(\n spec: AgentSpecBase,\n extra: { prompt?: string; messages?: Array<{ role: string; content: string }> } = {},\n): Record<string, unknown> {\n if (!spec.agentId && (typeof spec.systemPrompt !== \"string\" || spec.systemPrompt.length === 0)) {\n throw new MantyxError(\"Either `agentId` or `systemPrompt` is required\");\n }\n const body: Record<string, unknown> = {\n tools: serializeToolRefs(spec.tools ?? []),\n };\n if (typeof spec.systemPrompt === \"string\") body.systemPrompt = spec.systemPrompt;\n if (spec.agentId) body.agentId = spec.agentId;\n if (spec.name) body.name = spec.name;\n if (spec.modelId) body.modelId = spec.modelId;\n if (spec.budgets) body.budgets = spec.budgets;\n if (spec.metadata && Object.keys(spec.metadata).length > 0) body.metadata = spec.metadata;\n if (extra.prompt !== undefined) body.prompt = extra.prompt;\n if (extra.messages !== undefined) body.messages = extra.messages;\n return body;\n}\n\nfunction serializeToolRefs(tools: ToolRef[]): unknown[] {\n return tools.map((t) => {\n if (t.kind === \"mantyx\") return { kind: \"mantyx\", id: t.id };\n if (t.kind === \"mantyx_plugin\") return { kind: \"mantyx_plugin\", name: t.name };\n return {\n kind: \"local\",\n name: t.name,\n description: t.description,\n parameters: toToolParametersWire(t.parameters),\n };\n });\n}\n\nfunction collectLocalHandlers(tools: ToolRef[]): Map<string, LocalTool> {\n const map = new Map<string, LocalTool>();\n for (const t of tools) {\n if (isLocalTool(t)) map.set(t.name, t);\n }\n return map;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","/**\n * Release version — synced from repo root VERSION (`npm run sync-version`).\n */\nexport const SDK_VERSION = \"0.1.0\";\n"],"mappings":"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","names":[]}
@@ -0,0 +1,384 @@
1
+ # Agent Runs — wire protocol
2
+
3
+ This document specifies the public wire protocol that the MANTYX agent-runs API
4
+ speaks with SDKs. It is the source of truth for anyone implementing a new
5
+ client (Python, Rust, Java…) and is shipped with each first-party SDK so the
6
+ SDK repository can stand on its own when it is extracted from this monorepo.
7
+
8
+ The companion document for this protocol — server-side overview, internals,
9
+ deployment notes — is [`docs/agent-runs.md`](./agent-runs.md).
10
+
11
+ ## 1. Concepts
12
+
13
+ **Ephemeral agent.** A run-time agent that is *defined by the request* rather
14
+ than persisted as a row in MANTYX's `Agent` table. The full spec (system
15
+ prompt, model, tools) is stored as part of each session/run for observability
16
+ but is not editable from the dashboard.
17
+
18
+ **Tool refs.** Three flavours, all carried inside the agent spec:
19
+
20
+ | `kind` | Resolved by | Notes |
21
+ | ---------------- | ----------- | ----- |
22
+ | `mantyx` | server | A workspace `Tool` row referenced by id (HTTP / Code / Plugin). |
23
+ | `mantyx_plugin` | server | A platform plugin tool referenced by name. |
24
+ | `local` | client | Defined and executed in the SDK's process. |
25
+
26
+ When the model calls a `local` tool, MANTYX pauses the agent loop, emits a
27
+ `local_tool_call` event over SSE and waits for the SDK to POST a tool-result
28
+ back via HTTP.
29
+
30
+ **One-shot run vs. session.** A run is an LLM execution. Runs may be:
31
+
32
+ - *one-shot* (`POST /agent-runs`) — fire-and-stream, no persistent state apart
33
+ from observability.
34
+ - *session-scoped* (`POST /agent-sessions/:id/messages`) — the run inherits the
35
+ session's full message history, and the new user/assistant turns are
36
+ appended back to the session on success.
37
+
38
+ ## 2. Authentication
39
+
40
+ All SDK-facing endpoints sit under
41
+
42
+ ```
43
+ /api/v1/workspaces/{workspaceSlug}/...
44
+ ```
45
+
46
+ and are authenticated with a workspace API key with usage `developer_api`:
47
+
48
+ ```
49
+ Authorization: Bearer <api-key>
50
+ # or, equivalently:
51
+ X-API-Key: <api-key>
52
+ ```
53
+
54
+ The workspace slug in the URL must match the key's tenant. Mismatches return
55
+ `404 not_found`. Missing/invalid keys return `401 unauthorized`. Rate limits
56
+ follow the workspace's existing developer-API sliding-window policy.
57
+
58
+ ## 3. Models
59
+
60
+ ```
61
+ GET /api/v1/workspaces/{workspaceSlug}/models
62
+ ```
63
+
64
+ Returns models the API key's workspace can run, including BYOK providers and
65
+ platform-hosted offerings visible to the workspace's tier.
66
+
67
+ ```jsonc
68
+ {
69
+ "models": [
70
+ {
71
+ "id": "platform:cm6abc123",
72
+ "label": "Anthropic Claude Sonnet 4.5 (platform)",
73
+ "provider": "anthropic",
74
+ "vendorModelId": "claude-sonnet-4-5",
75
+ "source": "platform_offering",
76
+ "contextWindowTokens": 200000,
77
+ "pricing": { "inputPer1MUsd": 3.0, "outputPer1MUsd": 15.0, "cacheReadPer1MUsd": 0.3 }
78
+ },
79
+ {
80
+ "id": "provider:cm6def456",
81
+ "label": "OpenAI (workspace BYOK) — gpt-5.5",
82
+ "provider": "openai",
83
+ "vendorModelId": "gpt-5.5",
84
+ "source": "workspace_provider",
85
+ "contextWindowTokens": 200000,
86
+ "pricing": null
87
+ }
88
+ ],
89
+ "defaultModelId": "platform:cm6abc123"
90
+ }
91
+ ```
92
+
93
+ The `id` is the canonical value the SDK passes back as `RunSpec.modelId` /
94
+ `SessionSpec.modelId`. The server accepts three additional shorthand forms:
95
+
96
+ - `provider:<id>:<vendor>` — pin a specific BYOK provider but override the
97
+ vendor model id.
98
+ - `<vendorModelId>` — bare vendor id; only succeeds if exactly one workspace
99
+ provider can run it.
100
+ - `undefined`/omitted — falls back to the workspace default provider's
101
+ default model.
102
+
103
+ Invalid `modelId` values return `400 invalid_model` with a candidate list
104
+ in the body when applicable.
105
+
106
+ ## 4. Agent spec
107
+
108
+ The agent spec is the body shape used by `POST /agent-runs` and `POST
109
+ /agent-sessions`:
110
+
111
+ ```jsonc
112
+ {
113
+ "name": "ephemeral", // optional, observability only
114
+ "agentId": "agent_cm6abc123", // optional — see §4.1
115
+ "systemPrompt": "You are helpful.", // required unless agentId is set
116
+ "modelId": "platform:cm6abc123", // optional, see §3
117
+ "tools": [
118
+ { "kind": "mantyx", "id": "tool_cm6..." },
119
+ { "kind": "mantyx_plugin", "name": "web_search" },
120
+ {
121
+ "kind": "local",
122
+ "name": "read_file",
123
+ "description": "Read a file from the user's machine",
124
+ "parameters": { // JSON Schema for the args object
125
+ "type": "object",
126
+ "properties": { "path": { "type": "string" } },
127
+ "required": ["path"],
128
+ "additionalProperties": false
129
+ }
130
+ }
131
+ ],
132
+ "budgets": { "maxToolTurns": 32 }, // optional safety cap
133
+ "metadata": { // optional, see §4.2
134
+ "customer": "acme",
135
+ "env": "prod"
136
+ }
137
+ }
138
+ ```
139
+
140
+ `POST /agent-runs` additionally accepts `prompt` *or* `messages` (an array of
141
+ `{role, content}`). Sending both is a `400 invalid_request`.
142
+
143
+ ### 4.1 Triggering a persisted MANTYX agent (`agentId`)
144
+
145
+ Set `agentId` to the `id` of a workspace `Agent` to run that agent instead of
146
+ defining an ephemeral one inline. When `agentId` is set:
147
+
148
+ - `systemPrompt` becomes optional. If omitted, the server uses the agent's
149
+ stored system prompt at run time.
150
+ - `modelId` becomes optional. If omitted, the server uses the agent's
151
+ configured LLM provider (or the workspace automation provider if the agent
152
+ has *Use workspace default model* turned on).
153
+ - The agent's own tools are loaded from its workspace configuration —
154
+ including memory, skills, and plugin tools — and your `tools` array is
155
+ **merged on top**. This is typically used to attach `local` tools so the
156
+ agent can call back into your process for this run, without needing to
157
+ edit the agent's stored tool list.
158
+ - The API key must be authorized for this `agentId`. Keys created with an
159
+ empty `agentIds` allowlist (= "all agents") work for any agent in the
160
+ workspace; otherwise the agent must be in the key's allowlist or the call
161
+ returns `403 forbidden`.
162
+ - An unknown / cross-workspace `agentId` returns `403` (the API key check
163
+ fires first); a malformed `agentId` returns `400`.
164
+
165
+ Both `agentId` and `systemPrompt` may be supplied. The agent's stored prompt
166
+ wins; the inline `systemPrompt` is ignored.
167
+
168
+ ### 4.2 `metadata` (developer-supplied KV for filtering)
169
+
170
+ `metadata` is a flat string→string KV that is **persisted alongside the run /
171
+ session** and surfaced in the MANTYX dashboard. Use it to tag runs with your
172
+ own application identifiers (`customer`, `env`, `workflow`, `trace_id`, …) so
173
+ your team can filter the observability UI without reverse-engineering the
174
+ prompt.
175
+
176
+ Validation (server-side, `400 invalid_request` on violation):
177
+
178
+ | Constraint | Limit |
179
+ | ------------------------- | ---------------------------------- |
180
+ | Max entries | 16 |
181
+ | Key pattern | `^[A-Za-z0-9._-]{1,64}$` |
182
+ | Value type / length | string ≤ 256 chars |
183
+ | Serialized JSON size | ≤ 4 KB |
184
+
185
+ For session-scoped runs the inheritance rules are:
186
+
187
+ - `POST /agent-sessions { metadata }` — sets the session's metadata; this is
188
+ inherited by every run created through `POST /agent-sessions/:id/messages`.
189
+ - `POST /agent-sessions/:id/messages { metadata }` — optional per-message
190
+ override. The server snapshots `session.metadata` ⊕ override (run-level
191
+ keys win) onto the run row at creation time. Later edits to the session
192
+ metadata do not retroactively rewrite past runs.
193
+
194
+ Metadata is returned on every read: `GET /agent-runs/:id`,
195
+ `GET /agent-sessions/:id`, and the admin list/detail endpoints. Filtering on
196
+ the admin list endpoints uses repeated `?metadata=key:value` query params,
197
+ AND-combined; see `docs/agent-runs.md` §"Web UI" for details.
198
+
199
+ ## 5. One-shot runs
200
+
201
+ ```
202
+ POST /api/v1/workspaces/{slug}/agent-runs
203
+ GET /api/v1/workspaces/{slug}/agent-runs/{runId}
204
+ GET /api/v1/workspaces/{slug}/agent-runs/{runId}/stream
205
+ POST /api/v1/workspaces/{slug}/agent-runs/{runId}/tool-results
206
+ POST /api/v1/workspaces/{slug}/agent-runs/{runId}/cancel
207
+ ```
208
+
209
+ `POST /agent-runs` returns `202 Accepted` immediately:
210
+
211
+ ```json
212
+ { "runId": "run_abc", "streamUrl": "/api/v1/workspaces/acme/agent-runs/run_abc/stream" }
213
+ ```
214
+
215
+ `GET .../stream` is the canonical event channel; see §7.
216
+
217
+ `GET /agent-runs/{runId}` returns the run snapshot (status, final text, error,
218
+ spec) without subscribing to live events. Useful for polling long runs.
219
+
220
+ ## 6. Sessions
221
+
222
+ ```
223
+ POST /api/v1/workspaces/{slug}/agent-sessions
224
+ GET /api/v1/workspaces/{slug}/agent-sessions/{sessionId}
225
+ POST /api/v1/workspaces/{slug}/agent-sessions/{sessionId}/messages
226
+ DELETE /api/v1/workspaces/{slug}/agent-sessions/{sessionId}
227
+ ```
228
+
229
+ `POST /agent-sessions` creates a session with the agent spec. The body shape
230
+ is the same as the one-shot agent spec (no `prompt`, no `messages`).
231
+ Returns:
232
+
233
+ ```json
234
+ { "sessionId": "ses_abc" }
235
+ ```
236
+
237
+ `POST /agent-sessions/{id}/messages` queues a new run scoped to the session
238
+ and returns `{ runId, streamUrl }` just like a one-shot run. Body:
239
+
240
+ ```jsonc
241
+ {
242
+ "prompt": "What's in /etc/hosts?",
243
+ "tools": [/* optional refresh of tool definitions */]
244
+ }
245
+ ```
246
+
247
+ The server prepends the session's prior messages, runs the model, and on
248
+ success appends the new user/assistant turns back to the session row. Local
249
+ tool **handlers** are *not* persisted: the session stores definitions
250
+ (name, schema, description) so that a restarted SDK can re-bind handlers and
251
+ keep going.
252
+
253
+ `DELETE` flips the session to `ended` and cancels any in-flight run.
254
+
255
+ ## 7. SSE stream
256
+
257
+ `GET .../agent-runs/{runId}/stream` returns `text/event-stream`. Reconnects
258
+ support both:
259
+
260
+ - the standard `Last-Event-ID` request header (set automatically by browser
261
+ `EventSource`), and
262
+ - a `?lastSeq=<int>` query param (preferred for bare HTTP clients).
263
+
264
+ The server replays missed events from the `EphemeralAgentRunEvent` table in
265
+ order before resuming the live tail.
266
+
267
+ Each frame:
268
+
269
+ ```
270
+ id: 17
271
+ event: <type>
272
+ data: <utf-8 JSON>
273
+
274
+ ```
275
+
276
+ `<type>` and `<data>` shapes:
277
+
278
+ ```jsonc
279
+ // running message
280
+ { "seq": 1, "type": "started", "data": {} }
281
+
282
+ // streamed assistant tokens (zero or more per turn)
283
+ { "seq": 2, "type": "assistant_delta", "data": { "text": "Hello" } }
284
+
285
+ // completed assistant message (text + any tool calls about to execute)
286
+ { "seq": 3, "type": "assistant_message", "data": { "text": "...", "toolCalls": [...] } }
287
+
288
+ // server-side tool call/result (informational; SDK does not act on these)
289
+ { "seq": 4, "type": "tool_call", "data": { "toolUseId": "...", "name": "...", "input": {...} } }
290
+ { "seq": 5, "type": "tool_result", "data": { "toolUseId": "...", "name": "...", "ok": true, "summary": "..." } }
291
+
292
+ // LOCAL tool call — SDK MUST POST a tool-result for the same toolUseId
293
+ { "seq": 6, "type": "local_tool_call", "data": { "toolUseId": "tu_x", "name": "read_file", "input": { "path": "/etc/hosts" } } }
294
+
295
+ // echo of the SDK's POSTed tool-result, persisted for replay
296
+ { "seq": 7, "type": "local_tool_result_in", "data": { "toolUseId": "tu_x", "output": "127.0.0.1 ..." } }
297
+
298
+ // terminal event
299
+ { "seq": 8, "type": "result", "data": { "subtype": "success", "text": "Final reply" } }
300
+ { "seq": 8, "type": "result", "data": { "subtype": "error_local_tool_timeout", "error": "..." } }
301
+ { "seq": 8, "type": "cancelled", "data": {} }
302
+ ```
303
+
304
+ A run terminates with exactly one of `result` or `cancelled`. The connection
305
+ is closed by the server immediately after sending the terminal event. Clients
306
+ should not assume any particular ordering between the human-readable `event:`
307
+ field and the parsed `type` inside `data` — they are always equal, but
308
+ implementations should rely on `data.type` because some HTTP middleware
309
+ strips the `event:` line.
310
+
311
+ ## 8. Local tool result
312
+
313
+ ```
314
+ POST /api/v1/workspaces/{slug}/agent-runs/{runId}/tool-results
315
+ Content-Type: application/json
316
+
317
+ {
318
+ "toolUseId": "tu_x",
319
+ "result": "127.0.0.1 localhost" // OR
320
+ "error": "ENOENT: no such file"
321
+ }
322
+ ```
323
+
324
+ `200 OK` on accept. `404 unknown_tool_use` if the `toolUseId` is unknown,
325
+ already satisfied, or the run has terminated. `409 run_terminal` if the run
326
+ has already produced a `result` event.
327
+
328
+ `result` MUST be a string; SDKs serialize structured outputs as JSON before
329
+ posting. Errors are surfaced to the model as a tool-error response.
330
+
331
+ ## 9. Cancellation
332
+
333
+ ```
334
+ POST /api/v1/workspaces/{slug}/agent-runs/{runId}/cancel
335
+ ```
336
+
337
+ Idempotent. The run will produce a terminal `cancelled` event on the SSE
338
+ stream. In-flight `tool-results` posted after cancellation are accepted with
339
+ `200 OK` but ignored.
340
+
341
+ ## 10. Errors
342
+
343
+ All non-2xx responses use this body shape:
344
+
345
+ ```jsonc
346
+ {
347
+ "error": "invalid_model", // machine-readable code
348
+ "message": "Model 'foo' is ambiguous; pick one of: provider:cm6...",
349
+ "candidates": [/* sometimes present */]
350
+ }
351
+ ```
352
+
353
+ Common codes:
354
+
355
+ | Code | HTTP | Notes |
356
+ | ---------------------- | ---: | ----- |
357
+ | `unauthorized` | 401 | Missing/invalid API key |
358
+ | `not_found` | 404 | Workspace, run, or session unknown |
359
+ | `invalid_request` | 400 | Body failed Zod validation |
360
+ | `invalid_model` | 400 | `modelId` couldn't be resolved |
361
+ | `unknown_tool_use` | 404 | Tool-result for an unknown `toolUseId` |
362
+ | `run_terminal` | 409 | Tool-result after run finished |
363
+ | `rate_limited` | 429 | Per-API-key sliding window |
364
+
365
+ ## 11. Suggested client architecture
366
+
367
+ A reference SDK should:
368
+
369
+ 1. Hold the API key + workspace slug and a small `fetch` (or stdlib HTTP)
370
+ client.
371
+ 2. Maintain a registry of local tool handlers, keyed by `name`.
372
+ 3. On `runAgent` / `session.send`:
373
+ - POST the run/message, get `{ runId, streamUrl }`.
374
+ - Open the SSE stream with `Last-Event-ID` if reconnecting.
375
+ - On `local_tool_call`, look up the handler, validate args against the
376
+ tool's schema, run it, POST the result back to `.../tool-results`.
377
+ - On terminal `result`, resolve the call. On `error` subtype, throw.
378
+ 4. Re-emit assistant deltas/events as a stream/iterator for callers who care
379
+ about live output.
380
+ 5. Treat the protocol as the contract. Implementation details such as Valkey
381
+ pub/sub or pgvector are server-side only.
382
+
383
+ The TypeScript SDK in [`packages/mantyx-sdk/ts/`](../packages/mantyx-sdk/ts/) and the Go SDK in
384
+ [`packages/mantyx-sdk/go/`](../packages/mantyx-sdk/go/) are reference implementations of this protocol.
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@mantyx/sdk",
3
+ "version": "0.1.0",
4
+ "description": "MANTYX as a hosted agent runtime: define ephemeral agents, mix server-side MANTYX tools with locally-executed tools, run them remotely.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "docs",
19
+ "README.md",
20
+ "LICENSE",
21
+ "CHANGELOG.md",
22
+ "CONTRIBUTING.md",
23
+ "EXTRACT.md"
24
+ ],
25
+ "scripts": {
26
+ "sync-version": "node ../scripts/sync-version.mjs",
27
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --sourcemap",
28
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
29
+ "typecheck": "tsc --noEmit",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.17.0"
36
+ },
37
+ "dependencies": {
38
+ "zod": "^3.23.8"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.0.0",
42
+ "tsup": "^8.3.0",
43
+ "typescript": "^5.6.0",
44
+ "vitest": "^2.1.0"
45
+ },
46
+ "keywords": [
47
+ "mantyx",
48
+ "agent",
49
+ "llm",
50
+ "sdk",
51
+ "tool-use",
52
+ "agentic",
53
+ "ai"
54
+ ],
55
+ "license": "Apache-2.0",
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }