@mantyx/sdk 0.11.0 → 0.12.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.
- package/CHANGELOG.md +8 -1
- package/dist/a2a-server.cjs.map +1 -1
- package/dist/a2a-server.d.cts +1 -1
- package/dist/a2a-server.d.ts +1 -1
- package/dist/a2a-server.js +1 -1
- package/dist/{chunk-DR625E6B.js → chunk-2K4BGJGJ.js} +20 -1
- package/dist/chunk-2K4BGJGJ.js.map +1 -0
- package/dist/{client-Byb0Zdo7.d.cts → client-LQlx7iYY.d.cts} +63 -2
- package/dist/{client-Byb0Zdo7.d.ts → client-LQlx7iYY.d.ts} +63 -2
- package/dist/index.cjs +20 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/docs/agent-runs-protocol.md +81 -18
- package/docs/wire-protocol.md +115 -25
- package/package.json +1 -1
- package/dist/chunk-DR625E6B.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, D as DEFAULT_BASE_URL, e as DEFAULT_OAUTH_BASE_URL, f as DEFAULT_REFRESH_SKEW_MS, g as DefineLocalA2AOptions, h as DefineLocalMcpOptions, i as DefineLocalToolOptions, E as ErrorEvent, L as LocalA2ATool, j as LocalHandlers, k as LocalMcpHttpTransport, l as LocalMcpServer, m as LocalMcpStdioTransport, n as LocalTool, o as LocalToolCallEvent, p as LocalToolResultInEvent, q as LoopDetectedEvent, r as LoopDetection, s as MantyxA2AOptions, t as MantyxAuthError, M as MantyxClient, u as MantyxClientOptions, v as MantyxError, w as MantyxMcpOptions, x as MantyxNetworkError, y as MantyxOAuthClient, z as MantyxOAuthClientOptions, B as MantyxOAuthError, F as MantyxParseError, G as MantyxPluginToolRef, H as MantyxRunError, I as MantyxRunErrorInit, J as MantyxRunErrorModel, K as MantyxRunErrorTokens, N as MantyxScopeError, O as MantyxToolError, P as MantyxToolRef, Q as McpToolRef, S as ModelCatalog, U as ModelInfo, V as OAuthToken, W as OutputSchema, R as ReasoningLevel, X as RefreshOptions, Y as RefreshTokenSourceOptions, Z as ResultEvent, _ as RevokeOptions, $ as RunEvent, a0 as RunEventBase, a1 as RunModelInfo, a2 as RunResult, a3 as RunSpec, a4 as RunTokenUsage, a5 as ServerToolResultEvent, a6 as SessionInfo, a7 as SessionSpec, a8 as
|
|
1
|
+
export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, D as DEFAULT_BASE_URL, e as DEFAULT_OAUTH_BASE_URL, f as DEFAULT_REFRESH_SKEW_MS, g as DefineLocalA2AOptions, h as DefineLocalMcpOptions, i as DefineLocalToolOptions, E as ErrorEvent, L as LocalA2ATool, j as LocalHandlers, k as LocalMcpHttpTransport, l as LocalMcpServer, m as LocalMcpStdioTransport, n as LocalTool, o as LocalToolCallEvent, p as LocalToolResultInEvent, q as LoopDetectedEvent, r as LoopDetection, s as MantyxA2AOptions, t as MantyxAuthError, M as MantyxClient, u as MantyxClientOptions, v as MantyxError, w as MantyxMcpOptions, x as MantyxNetworkError, y as MantyxOAuthClient, z as MantyxOAuthClientOptions, B as MantyxOAuthError, F as MantyxParseError, G as MantyxPluginToolRef, H as MantyxRunError, I as MantyxRunErrorInit, J as MantyxRunErrorModel, K as MantyxRunErrorTokens, N as MantyxScopeError, O as MantyxToolError, P as MantyxToolRef, Q as McpToolRef, S as ModelCatalog, U as ModelInfo, V as OAuthToken, W as OutputSchema, R as ReasoningLevel, X as RefreshOptions, Y as RefreshTokenSourceOptions, Z as ResultEvent, _ as RevokeOptions, $ as RunEvent, a0 as RunEventBase, a1 as RunModelInfo, a2 as RunResult, a3 as RunSpec, a4 as RunTokenUsage, a5 as ServerToolResultEvent, a6 as SessionInfo, a7 as SessionSpec, a8 as Supervisor, a9 as SupervisorAction, aa as SupervisorEvent, ab as ThinkingDeltaEvent, ac as TokenRequestReason, ad as TokenSource, ae as ToolBudget, af as ToolBudgetExceededEvent, ag as ToolBudgets, T as ToolRef, ah as ZodLikeObject, ai as defineLocalA2A, aj as defineLocalMcp, ak as defineLocalTool, al as isLocalA2ATool, am as isLocalMcpServer, an as isLocalTool, ao as mantyxA2A, ap as mantyxMcp, aq as mantyxPluginTool, ar as mantyxTool, as as parseRunOutput } from './client-LQlx7iYY.cjs';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -52,6 +52,6 @@ declare function readSseStream(body: ReadableStream<Uint8Array> | null, opts?: S
|
|
|
52
52
|
/**
|
|
53
53
|
* Release version — synced from repo root VERSION (`npm run sync-version`).
|
|
54
54
|
*/
|
|
55
|
-
declare const SDK_VERSION = "0.
|
|
55
|
+
declare const SDK_VERSION = "0.12.0";
|
|
56
56
|
|
|
57
57
|
export { SDK_VERSION, type SseEvent, type SseStreamOptions, readSseStream, toToolParametersWire, zodToJsonSchema };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, D as DEFAULT_BASE_URL, e as DEFAULT_OAUTH_BASE_URL, f as DEFAULT_REFRESH_SKEW_MS, g as DefineLocalA2AOptions, h as DefineLocalMcpOptions, i as DefineLocalToolOptions, E as ErrorEvent, L as LocalA2ATool, j as LocalHandlers, k as LocalMcpHttpTransport, l as LocalMcpServer, m as LocalMcpStdioTransport, n as LocalTool, o as LocalToolCallEvent, p as LocalToolResultInEvent, q as LoopDetectedEvent, r as LoopDetection, s as MantyxA2AOptions, t as MantyxAuthError, M as MantyxClient, u as MantyxClientOptions, v as MantyxError, w as MantyxMcpOptions, x as MantyxNetworkError, y as MantyxOAuthClient, z as MantyxOAuthClientOptions, B as MantyxOAuthError, F as MantyxParseError, G as MantyxPluginToolRef, H as MantyxRunError, I as MantyxRunErrorInit, J as MantyxRunErrorModel, K as MantyxRunErrorTokens, N as MantyxScopeError, O as MantyxToolError, P as MantyxToolRef, Q as McpToolRef, S as ModelCatalog, U as ModelInfo, V as OAuthToken, W as OutputSchema, R as ReasoningLevel, X as RefreshOptions, Y as RefreshTokenSourceOptions, Z as ResultEvent, _ as RevokeOptions, $ as RunEvent, a0 as RunEventBase, a1 as RunModelInfo, a2 as RunResult, a3 as RunSpec, a4 as RunTokenUsage, a5 as ServerToolResultEvent, a6 as SessionInfo, a7 as SessionSpec, a8 as
|
|
1
|
+
export { A as A2AToolRef, a as AgentSession, b as AgentSpecBase, c as AssistantDeltaEvent, d as AssistantMessageEvent, C as CancelledEvent, D as DEFAULT_BASE_URL, e as DEFAULT_OAUTH_BASE_URL, f as DEFAULT_REFRESH_SKEW_MS, g as DefineLocalA2AOptions, h as DefineLocalMcpOptions, i as DefineLocalToolOptions, E as ErrorEvent, L as LocalA2ATool, j as LocalHandlers, k as LocalMcpHttpTransport, l as LocalMcpServer, m as LocalMcpStdioTransport, n as LocalTool, o as LocalToolCallEvent, p as LocalToolResultInEvent, q as LoopDetectedEvent, r as LoopDetection, s as MantyxA2AOptions, t as MantyxAuthError, M as MantyxClient, u as MantyxClientOptions, v as MantyxError, w as MantyxMcpOptions, x as MantyxNetworkError, y as MantyxOAuthClient, z as MantyxOAuthClientOptions, B as MantyxOAuthError, F as MantyxParseError, G as MantyxPluginToolRef, H as MantyxRunError, I as MantyxRunErrorInit, J as MantyxRunErrorModel, K as MantyxRunErrorTokens, N as MantyxScopeError, O as MantyxToolError, P as MantyxToolRef, Q as McpToolRef, S as ModelCatalog, U as ModelInfo, V as OAuthToken, W as OutputSchema, R as ReasoningLevel, X as RefreshOptions, Y as RefreshTokenSourceOptions, Z as ResultEvent, _ as RevokeOptions, $ as RunEvent, a0 as RunEventBase, a1 as RunModelInfo, a2 as RunResult, a3 as RunSpec, a4 as RunTokenUsage, a5 as ServerToolResultEvent, a6 as SessionInfo, a7 as SessionSpec, a8 as Supervisor, a9 as SupervisorAction, aa as SupervisorEvent, ab as ThinkingDeltaEvent, ac as TokenRequestReason, ad as TokenSource, ae as ToolBudget, af as ToolBudgetExceededEvent, ag as ToolBudgets, T as ToolRef, ah as ZodLikeObject, ai as defineLocalA2A, aj as defineLocalMcp, ak as defineLocalTool, al as isLocalA2ATool, am as isLocalMcpServer, an as isLocalTool, ao as mantyxA2A, ap as mantyxMcp, aq as mantyxPluginTool, ar as mantyxTool, as as parseRunOutput } from './client-LQlx7iYY.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -52,6 +52,6 @@ declare function readSseStream(body: ReadableStream<Uint8Array> | null, opts?: S
|
|
|
52
52
|
/**
|
|
53
53
|
* Release version — synced from repo root VERSION (`npm run sync-version`).
|
|
54
54
|
*/
|
|
55
|
-
declare const SDK_VERSION = "0.
|
|
55
|
+
declare const SDK_VERSION = "0.12.0";
|
|
56
56
|
|
|
57
57
|
export { SDK_VERSION, type SseEvent, type SseStreamOptions, readSseStream, toToolParametersWire, zodToJsonSchema };
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
readSseStream,
|
|
24
24
|
toToolParametersWire,
|
|
25
25
|
zodToJsonSchema
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2K4BGJGJ.js";
|
|
27
27
|
|
|
28
28
|
// src/oauth.ts
|
|
29
29
|
var DEFAULT_OAUTH_BASE_URL = "https://app.mantyx.io";
|
|
@@ -245,7 +245,7 @@ function normalizeScope(scope) {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
// src/version.ts
|
|
248
|
-
var SDK_VERSION = "0.
|
|
248
|
+
var SDK_VERSION = "0.12.0";
|
|
249
249
|
export {
|
|
250
250
|
AgentSession,
|
|
251
251
|
DEFAULT_BASE_URL,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/oauth.ts","../src/version.ts"],"sourcesContent":["/**\n * MANTYX OAuth 2.0 refresh client: trade a stored refresh token for\n * short-lived access tokens, revoke tokens at sign-out, and expose\n * a {@link TokenSource} the {@link MantyxClient} HTTP layer calls\n * before every request (and again on 401).\n *\n * The library is intentionally **refresh-only**. It assumes the caller\n * already obtained the refresh token through their own sign-in flow\n * (Authorization Code + PKCE in a browser, native redirect, server-\n * side exchange — whatever fits the host application). The SDK does\n * not drive consent, does not initiate auth-code exchanges, and does\n * not bundle PKCE helpers.\n *\n * Wire contract (`docs/oauth.md`):\n *\n * - Token endpoint: `POST <baseUrl>/api/oauth/token`, form-encoded,\n * `grant_type=refresh_token`. Echoes back the same `refresh_token`\n * the client sent (refresh tokens are persistent and non-rotating).\n * - Revoke endpoint: `POST <baseUrl>/api/oauth/revoke`, form-encoded.\n * - Access tokens (`mantyx_at_…`) live 1 hour (`expires_in: 3600`).\n * - Refresh tokens (`mantyx_rt_…`) are long-lived; the caller persists\n * them once at first sign-in (encrypted at rest) and the SDK re-mints\n * access tokens from the same value on demand.\n */\n\nimport { MantyxError, MantyxNetworkError } from \"./errors.js\";\n\nexport const DEFAULT_OAUTH_BASE_URL = \"https://app.mantyx.io\";\n\n/** Skew (ms) before `expiresAt` at which a TokenSource will pre-emptively refresh. Default 60s. */\nexport const DEFAULT_REFRESH_SKEW_MS = 60_000;\n\n/**\n * Raised on a non-2xx response from `POST /api/oauth/token` or\n * `POST /api/oauth/revoke`. Carries the RFC 6749 `error` discriminator\n * (`\"invalid_grant\"`, `\"invalid_client\"`, `\"unsupported_grant_type\"`,\n * …) and the optional `error_description` so callers can branch on\n * machine-readable values without parsing the human message.\n *\n * `invalid_grant` from the refresh path specifically signals that the\n * refresh token has been revoked (or the OAuth grant / application\n * was deleted). The SDK never loops on this — callers should route\n * the user back to a fresh sign-in.\n */\nexport class MantyxOAuthError extends MantyxError {\n readonly oauthError: string;\n readonly oauthErrorDescription: string | undefined;\n\n constructor(\n oauthError: string,\n oauthErrorDescription: string | undefined,\n status: number,\n ) {\n const message = oauthErrorDescription\n ? `OAuth ${oauthError}: ${oauthErrorDescription}`\n : `OAuth ${oauthError}`;\n super(message, { code: oauthError, status });\n this.name = \"MantyxOAuthError\";\n this.oauthError = oauthError;\n this.oauthErrorDescription = oauthErrorDescription;\n }\n}\n\n/**\n * Decoded `POST /api/oauth/token` response, augmented with an absolute\n * `expiresAt` timestamp the SDK uses to decide when to refresh.\n *\n * On the refresh grant the response's `refreshToken` is identical to\n * the value the client just sent (refresh tokens never rotate). The\n * field is surfaced for symmetry with whatever the calling app's\n * sign-in flow already does.\n */\nexport interface OAuthToken {\n readonly accessToken: string;\n readonly refreshToken: string | undefined;\n readonly tokenType: string;\n readonly expiresIn: number;\n /** Absolute Unix-ms timestamp set when the SDK parsed the response. */\n readonly expiresAt: number;\n readonly scope: string | undefined;\n}\n\n/** Why the SDK asked the {@link TokenSource} for the current access token. */\nexport type TokenRequestReason = \"initial\" | \"expired\" | \"unauthorized\";\n\n/**\n * A `TokenSource` produces the current access token on demand. The\n * {@link MantyxClient} HTTP layer calls it before every request. When\n * called with `reason: \"unauthorized\"` the source MUST force a refresh\n * (do not return a cached value); this is how the SDK recovers from\n * 401s caused by a token that the server already invalidated.\n *\n * Implementations should be safe to call from many concurrent requests.\n */\nexport type TokenSource = (reason?: TokenRequestReason) => Promise<string>;\n\n/** Caller-supplied options for `MantyxOAuthClient`. */\nexport interface MantyxOAuthClientOptions {\n /**\n * OAuth `client_id` issued at app registration (token prefix\n * `mantyx_oa_`).\n */\n clientId: string;\n /**\n * OAuth `client_secret` issued at app registration (token prefix\n * `mantyx_oas_`). Every MANTYX OAuth app is a confidential client,\n * so this is always required for token + revoke calls. Treat as a\n * deployment secret — do not bundle into browser builds.\n */\n clientSecret: string;\n /**\n * Origin of the MANTYX deployment. Defaults to `https://app.mantyx.io`.\n * The OAuth endpoints are mounted at `<baseUrl>/api/oauth/...`.\n */\n baseUrl?: string;\n /** Optional `fetch` override (e.g. node-fetch wrapper). Default: global `fetch`. */\n fetch?: typeof fetch;\n /** Default per-request timeout in milliseconds. Default: 30s. */\n timeoutMs?: number;\n}\n\nexport interface RefreshOptions {\n refreshToken: string;\n /**\n * Optional scope narrowing. Must be a subset of the scopes already\n * granted to the refresh token (server enforces this). Useful when\n * an SDK consumer wants a short-scope access token for a specific\n * sub-operation.\n */\n scope?: string | readonly string[];\n}\n\nexport interface RevokeOptions {\n token: string;\n}\n\nexport interface RefreshTokenSourceOptions {\n refreshToken: string;\n /** Optional scope narrowing applied on every refresh. */\n scope?: string | readonly string[];\n /**\n * How many ms before `expiresAt` the source proactively refreshes.\n * Defaults to {@link DEFAULT_REFRESH_SKEW_MS} (60s).\n */\n refreshSkewMs?: number;\n /**\n * Optional initial access token + expiry to seed the source's cache\n * with (e.g. the token already in hand from the host application's\n * sign-in flow). When omitted, the source mints one on the first\n * call.\n */\n initialToken?: OAuthToken;\n}\n\n/**\n * Refresh-only wrapper around the MANTYX OAuth 2.0 authorization-server\n * endpoints. App-scoped (one per `{clientId, clientSecret}` pair);\n * construct independently of {@link MantyxClient}, then either call\n * {@link refresh} / {@link revoke} directly or hand a `TokenSource`\n * produced by {@link refreshTokenSource} to `MantyxClient` for fully\n * transparent refresh on every request.\n *\n * The client deliberately does **not** drive the authorization-code\n * exchange or any other \"initiate sign-in\" grant. The caller is\n * expected to obtain the refresh token through their own consent flow\n * and persist it before constructing this client.\n */\nexport class MantyxOAuthClient {\n readonly clientId: string;\n readonly baseUrl: string;\n private readonly clientSecret: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(opts: MantyxOAuthClientOptions) {\n if (!opts.clientId) {\n throw new MantyxError(\"`clientId` is required for MantyxOAuthClient\");\n }\n if (!opts.clientSecret) {\n throw new MantyxError(\"`clientSecret` is required for MantyxOAuthClient\");\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 MantyxOAuthClientOptions.\",\n );\n }\n this.clientId = opts.clientId;\n this.clientSecret = opts.clientSecret;\n this.baseUrl = (opts.baseUrl ?? DEFAULT_OAUTH_BASE_URL).replace(/\\/+$/, \"\");\n this.fetchImpl = f;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n /**\n * Mint a fresh access token from a stored refresh token. The\n * returned `refreshToken` is identical to the input — refresh\n * tokens are persistent and non-rotating, so the field is\n * surfaced only for symmetry with the response shape.\n *\n * On `400 invalid_grant` the refresh token has been revoked (or its\n * grant / app was deleted); the SDK surfaces a\n * {@link MantyxOAuthError} and callers must drive a fresh sign-in.\n */\n async refresh(opts: RefreshOptions): Promise<OAuthToken> {\n if (!opts.refreshToken) {\n throw new MantyxError(\"`refreshToken` is required for MantyxOAuthClient.refresh\");\n }\n const body: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: opts.refreshToken,\n };\n const scope = normalizeScope(opts.scope);\n if (scope !== undefined) body.scope = scope;\n return this.token(body);\n }\n\n /**\n * Revoke an access or refresh token (RFC 7009). The server always\n * returns 200, even for unknown tokens. Revoking a **refresh**\n * token kills the refresh and every live access token tied to its\n * grant; revoking an **access** token kills only that one.\n */\n async revoke(opts: RevokeOptions): Promise<void> {\n if (!opts.token) {\n throw new MantyxError(\"`token` is required for MantyxOAuthClient.revoke\");\n }\n await this.formPost(\"/api/oauth/revoke\", {\n token: opts.token,\n });\n }\n\n /**\n * Build a long-lived {@link TokenSource} that re-mints access\n * tokens from the supplied refresh token. Pass the returned source\n * to `new MantyxClient({ tokenSource, workspaceSlug, ... })`. The\n * source caches the access token in-memory and refreshes\n * proactively when the cached value is within `refreshSkewMs` of\n * `expiresAt`, or eagerly when `MantyxClient` reports a 401.\n *\n * Pass `initialToken` if the calling app already has a non-expired\n * access token in hand (e.g. straight out of the sign-in flow) to\n * avoid an extra round-trip on the first request.\n */\n refreshTokenSource(opts: RefreshTokenSourceOptions): TokenSource {\n if (!opts.refreshToken) {\n throw new MantyxError(\"`refreshToken` is required for MantyxOAuthClient.refreshTokenSource\");\n }\n const skew = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;\n const cache: TokenCache = { token: opts.initialToken, inflight: null };\n const refreshToken = opts.refreshToken;\n return makeTokenSource(cache, skew, async () => {\n return this.refresh({ refreshToken, scope: opts.scope });\n });\n }\n\n // -------------------------------------------------------------- internals\n\n /**\n * POST `application/x-www-form-urlencoded` to `/api/oauth/token` and\n * decode the {@link OAuthToken} response. Always injects `client_id`\n * + `client_secret` from the constructor.\n */\n private async token(body: Record<string, string>): Promise<OAuthToken> {\n const res = await this.formPost(\"/api/oauth/token\", body);\n let parsed: Record<string, unknown> = {};\n try {\n parsed = (await res.json()) as Record<string, unknown>;\n } catch {\n throw new MantyxOAuthError(\n \"invalid_response\",\n \"Token endpoint returned a non-JSON response\",\n res.status,\n );\n }\n const accessToken = typeof parsed.access_token === \"string\" ? parsed.access_token : \"\";\n if (!accessToken) {\n throw new MantyxOAuthError(\n \"invalid_response\",\n \"Token endpoint response is missing `access_token`\",\n res.status,\n );\n }\n const expiresIn = typeof parsed.expires_in === \"number\" ? parsed.expires_in : 3600;\n return {\n accessToken,\n refreshToken: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : undefined,\n tokenType: typeof parsed.token_type === \"string\" ? parsed.token_type : \"Bearer\",\n expiresIn,\n expiresAt: Date.now() + expiresIn * 1000,\n scope: typeof parsed.scope === \"string\" ? parsed.scope : undefined,\n };\n }\n\n private async formPost(path: string, body: Record<string, string>): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n const params = new URLSearchParams({\n ...body,\n client_id: this.clientId,\n client_secret: this.clientSecret,\n });\n const ctrl = new AbortController();\n const t = setTimeout(() => ctrl.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n signal: ctrl.signal,\n });\n } catch (err) {\n if (ctrl.signal.aborted) {\n throw new MantyxNetworkError(`OAuth request timed out after ${this.timeoutMs}ms`);\n }\n throw new MantyxNetworkError(`OAuth network error: ${(err as Error).message}`, {\n cause: err,\n });\n } finally {\n clearTimeout(t);\n }\n if (!res.ok) {\n let errBody: { error?: unknown; error_description?: unknown } = {};\n try {\n errBody = (await res.json()) as typeof errBody;\n } catch {\n // ignore\n }\n const oauthError = typeof errBody.error === \"string\" ? errBody.error : `http_${res.status}`;\n const desc =\n typeof errBody.error_description === \"string\" ? errBody.error_description : undefined;\n throw new MantyxOAuthError(oauthError, desc, res.status);\n }\n return res;\n }\n}\n\n// -------------------------------------------------------------- internals\n\ninterface TokenCache {\n token: OAuthToken | undefined;\n inflight: Promise<OAuthToken> | null;\n}\n\n/**\n * Wrap a `mintToken` thunk into a single-flight {@link TokenSource}\n * with a cache + proactive-refresh skew. The cache is overwritten\n * atomically on every successful mint; the in-flight promise\n * collapses N concurrent expired-token observers into one mint call.\n *\n * Single-flight is an efficiency, not a correctness requirement —\n * `docs/oauth.md` explicitly allows multiple concurrent refreshes\n * against the same refresh token — but it keeps the token-endpoint\n * QPS reasonable when an SDK consumer fans out work in parallel.\n */\nfunction makeTokenSource(\n cache: TokenCache,\n skewMs: number,\n mint: () => Promise<OAuthToken>,\n): TokenSource {\n return async (reason: TokenRequestReason = \"initial\"): Promise<string> => {\n if (reason !== \"unauthorized\" && cache.token && !isExpiring(cache.token, skewMs)) {\n return cache.token.accessToken;\n }\n if (cache.inflight) {\n const t = await cache.inflight;\n if (reason === \"unauthorized\" && t === cache.token) {\n // If the inflight refresh was triggered by a benign cache miss\n // and we observed an unauthorized hint after it started, fall\n // through and mint again so the caller never gets a stale token.\n } else {\n return t.accessToken;\n }\n }\n cache.inflight = mint().then(\n (t) => {\n cache.token = t;\n return t;\n },\n (err: unknown) => {\n throw err;\n },\n );\n try {\n const t = await cache.inflight;\n return t.accessToken;\n } finally {\n cache.inflight = null;\n }\n };\n}\n\nfunction isExpiring(token: OAuthToken, skewMs: number): boolean {\n return token.expiresAt - Date.now() <= skewMs;\n}\n\nfunction normalizeScope(scope: string | readonly string[] | undefined): string | undefined {\n if (scope === undefined) return undefined;\n if (typeof scope === \"string\") {\n const trimmed = scope.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n const joined = scope.filter((s) => typeof s === \"string\" && s.length > 0).join(\" \");\n return joined.length > 0 ? joined : undefined;\n}\n","/**\n * Release version — synced from repo root VERSION (`npm run sync-version`).\n */\nexport const SDK_VERSION = \"0.11.0\";\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/oauth.ts","../src/version.ts"],"sourcesContent":["/**\n * MANTYX OAuth 2.0 refresh client: trade a stored refresh token for\n * short-lived access tokens, revoke tokens at sign-out, and expose\n * a {@link TokenSource} the {@link MantyxClient} HTTP layer calls\n * before every request (and again on 401).\n *\n * The library is intentionally **refresh-only**. It assumes the caller\n * already obtained the refresh token through their own sign-in flow\n * (Authorization Code + PKCE in a browser, native redirect, server-\n * side exchange — whatever fits the host application). The SDK does\n * not drive consent, does not initiate auth-code exchanges, and does\n * not bundle PKCE helpers.\n *\n * Wire contract (`docs/oauth.md`):\n *\n * - Token endpoint: `POST <baseUrl>/api/oauth/token`, form-encoded,\n * `grant_type=refresh_token`. Echoes back the same `refresh_token`\n * the client sent (refresh tokens are persistent and non-rotating).\n * - Revoke endpoint: `POST <baseUrl>/api/oauth/revoke`, form-encoded.\n * - Access tokens (`mantyx_at_…`) live 1 hour (`expires_in: 3600`).\n * - Refresh tokens (`mantyx_rt_…`) are long-lived; the caller persists\n * them once at first sign-in (encrypted at rest) and the SDK re-mints\n * access tokens from the same value on demand.\n */\n\nimport { MantyxError, MantyxNetworkError } from \"./errors.js\";\n\nexport const DEFAULT_OAUTH_BASE_URL = \"https://app.mantyx.io\";\n\n/** Skew (ms) before `expiresAt` at which a TokenSource will pre-emptively refresh. Default 60s. */\nexport const DEFAULT_REFRESH_SKEW_MS = 60_000;\n\n/**\n * Raised on a non-2xx response from `POST /api/oauth/token` or\n * `POST /api/oauth/revoke`. Carries the RFC 6749 `error` discriminator\n * (`\"invalid_grant\"`, `\"invalid_client\"`, `\"unsupported_grant_type\"`,\n * …) and the optional `error_description` so callers can branch on\n * machine-readable values without parsing the human message.\n *\n * `invalid_grant` from the refresh path specifically signals that the\n * refresh token has been revoked (or the OAuth grant / application\n * was deleted). The SDK never loops on this — callers should route\n * the user back to a fresh sign-in.\n */\nexport class MantyxOAuthError extends MantyxError {\n readonly oauthError: string;\n readonly oauthErrorDescription: string | undefined;\n\n constructor(\n oauthError: string,\n oauthErrorDescription: string | undefined,\n status: number,\n ) {\n const message = oauthErrorDescription\n ? `OAuth ${oauthError}: ${oauthErrorDescription}`\n : `OAuth ${oauthError}`;\n super(message, { code: oauthError, status });\n this.name = \"MantyxOAuthError\";\n this.oauthError = oauthError;\n this.oauthErrorDescription = oauthErrorDescription;\n }\n}\n\n/**\n * Decoded `POST /api/oauth/token` response, augmented with an absolute\n * `expiresAt` timestamp the SDK uses to decide when to refresh.\n *\n * On the refresh grant the response's `refreshToken` is identical to\n * the value the client just sent (refresh tokens never rotate). The\n * field is surfaced for symmetry with whatever the calling app's\n * sign-in flow already does.\n */\nexport interface OAuthToken {\n readonly accessToken: string;\n readonly refreshToken: string | undefined;\n readonly tokenType: string;\n readonly expiresIn: number;\n /** Absolute Unix-ms timestamp set when the SDK parsed the response. */\n readonly expiresAt: number;\n readonly scope: string | undefined;\n}\n\n/** Why the SDK asked the {@link TokenSource} for the current access token. */\nexport type TokenRequestReason = \"initial\" | \"expired\" | \"unauthorized\";\n\n/**\n * A `TokenSource` produces the current access token on demand. The\n * {@link MantyxClient} HTTP layer calls it before every request. When\n * called with `reason: \"unauthorized\"` the source MUST force a refresh\n * (do not return a cached value); this is how the SDK recovers from\n * 401s caused by a token that the server already invalidated.\n *\n * Implementations should be safe to call from many concurrent requests.\n */\nexport type TokenSource = (reason?: TokenRequestReason) => Promise<string>;\n\n/** Caller-supplied options for `MantyxOAuthClient`. */\nexport interface MantyxOAuthClientOptions {\n /**\n * OAuth `client_id` issued at app registration (token prefix\n * `mantyx_oa_`).\n */\n clientId: string;\n /**\n * OAuth `client_secret` issued at app registration (token prefix\n * `mantyx_oas_`). Every MANTYX OAuth app is a confidential client,\n * so this is always required for token + revoke calls. Treat as a\n * deployment secret — do not bundle into browser builds.\n */\n clientSecret: string;\n /**\n * Origin of the MANTYX deployment. Defaults to `https://app.mantyx.io`.\n * The OAuth endpoints are mounted at `<baseUrl>/api/oauth/...`.\n */\n baseUrl?: string;\n /** Optional `fetch` override (e.g. node-fetch wrapper). Default: global `fetch`. */\n fetch?: typeof fetch;\n /** Default per-request timeout in milliseconds. Default: 30s. */\n timeoutMs?: number;\n}\n\nexport interface RefreshOptions {\n refreshToken: string;\n /**\n * Optional scope narrowing. Must be a subset of the scopes already\n * granted to the refresh token (server enforces this). Useful when\n * an SDK consumer wants a short-scope access token for a specific\n * sub-operation.\n */\n scope?: string | readonly string[];\n}\n\nexport interface RevokeOptions {\n token: string;\n}\n\nexport interface RefreshTokenSourceOptions {\n refreshToken: string;\n /** Optional scope narrowing applied on every refresh. */\n scope?: string | readonly string[];\n /**\n * How many ms before `expiresAt` the source proactively refreshes.\n * Defaults to {@link DEFAULT_REFRESH_SKEW_MS} (60s).\n */\n refreshSkewMs?: number;\n /**\n * Optional initial access token + expiry to seed the source's cache\n * with (e.g. the token already in hand from the host application's\n * sign-in flow). When omitted, the source mints one on the first\n * call.\n */\n initialToken?: OAuthToken;\n}\n\n/**\n * Refresh-only wrapper around the MANTYX OAuth 2.0 authorization-server\n * endpoints. App-scoped (one per `{clientId, clientSecret}` pair);\n * construct independently of {@link MantyxClient}, then either call\n * {@link refresh} / {@link revoke} directly or hand a `TokenSource`\n * produced by {@link refreshTokenSource} to `MantyxClient` for fully\n * transparent refresh on every request.\n *\n * The client deliberately does **not** drive the authorization-code\n * exchange or any other \"initiate sign-in\" grant. The caller is\n * expected to obtain the refresh token through their own consent flow\n * and persist it before constructing this client.\n */\nexport class MantyxOAuthClient {\n readonly clientId: string;\n readonly baseUrl: string;\n private readonly clientSecret: string;\n private readonly fetchImpl: typeof fetch;\n private readonly timeoutMs: number;\n\n constructor(opts: MantyxOAuthClientOptions) {\n if (!opts.clientId) {\n throw new MantyxError(\"`clientId` is required for MantyxOAuthClient\");\n }\n if (!opts.clientSecret) {\n throw new MantyxError(\"`clientSecret` is required for MantyxOAuthClient\");\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 MantyxOAuthClientOptions.\",\n );\n }\n this.clientId = opts.clientId;\n this.clientSecret = opts.clientSecret;\n this.baseUrl = (opts.baseUrl ?? DEFAULT_OAUTH_BASE_URL).replace(/\\/+$/, \"\");\n this.fetchImpl = f;\n this.timeoutMs = opts.timeoutMs ?? 30_000;\n }\n\n /**\n * Mint a fresh access token from a stored refresh token. The\n * returned `refreshToken` is identical to the input — refresh\n * tokens are persistent and non-rotating, so the field is\n * surfaced only for symmetry with the response shape.\n *\n * On `400 invalid_grant` the refresh token has been revoked (or its\n * grant / app was deleted); the SDK surfaces a\n * {@link MantyxOAuthError} and callers must drive a fresh sign-in.\n */\n async refresh(opts: RefreshOptions): Promise<OAuthToken> {\n if (!opts.refreshToken) {\n throw new MantyxError(\"`refreshToken` is required for MantyxOAuthClient.refresh\");\n }\n const body: Record<string, string> = {\n grant_type: \"refresh_token\",\n refresh_token: opts.refreshToken,\n };\n const scope = normalizeScope(opts.scope);\n if (scope !== undefined) body.scope = scope;\n return this.token(body);\n }\n\n /**\n * Revoke an access or refresh token (RFC 7009). The server always\n * returns 200, even for unknown tokens. Revoking a **refresh**\n * token kills the refresh and every live access token tied to its\n * grant; revoking an **access** token kills only that one.\n */\n async revoke(opts: RevokeOptions): Promise<void> {\n if (!opts.token) {\n throw new MantyxError(\"`token` is required for MantyxOAuthClient.revoke\");\n }\n await this.formPost(\"/api/oauth/revoke\", {\n token: opts.token,\n });\n }\n\n /**\n * Build a long-lived {@link TokenSource} that re-mints access\n * tokens from the supplied refresh token. Pass the returned source\n * to `new MantyxClient({ tokenSource, workspaceSlug, ... })`. The\n * source caches the access token in-memory and refreshes\n * proactively when the cached value is within `refreshSkewMs` of\n * `expiresAt`, or eagerly when `MantyxClient` reports a 401.\n *\n * Pass `initialToken` if the calling app already has a non-expired\n * access token in hand (e.g. straight out of the sign-in flow) to\n * avoid an extra round-trip on the first request.\n */\n refreshTokenSource(opts: RefreshTokenSourceOptions): TokenSource {\n if (!opts.refreshToken) {\n throw new MantyxError(\"`refreshToken` is required for MantyxOAuthClient.refreshTokenSource\");\n }\n const skew = opts.refreshSkewMs ?? DEFAULT_REFRESH_SKEW_MS;\n const cache: TokenCache = { token: opts.initialToken, inflight: null };\n const refreshToken = opts.refreshToken;\n return makeTokenSource(cache, skew, async () => {\n return this.refresh({ refreshToken, scope: opts.scope });\n });\n }\n\n // -------------------------------------------------------------- internals\n\n /**\n * POST `application/x-www-form-urlencoded` to `/api/oauth/token` and\n * decode the {@link OAuthToken} response. Always injects `client_id`\n * + `client_secret` from the constructor.\n */\n private async token(body: Record<string, string>): Promise<OAuthToken> {\n const res = await this.formPost(\"/api/oauth/token\", body);\n let parsed: Record<string, unknown> = {};\n try {\n parsed = (await res.json()) as Record<string, unknown>;\n } catch {\n throw new MantyxOAuthError(\n \"invalid_response\",\n \"Token endpoint returned a non-JSON response\",\n res.status,\n );\n }\n const accessToken = typeof parsed.access_token === \"string\" ? parsed.access_token : \"\";\n if (!accessToken) {\n throw new MantyxOAuthError(\n \"invalid_response\",\n \"Token endpoint response is missing `access_token`\",\n res.status,\n );\n }\n const expiresIn = typeof parsed.expires_in === \"number\" ? parsed.expires_in : 3600;\n return {\n accessToken,\n refreshToken: typeof parsed.refresh_token === \"string\" ? parsed.refresh_token : undefined,\n tokenType: typeof parsed.token_type === \"string\" ? parsed.token_type : \"Bearer\",\n expiresIn,\n expiresAt: Date.now() + expiresIn * 1000,\n scope: typeof parsed.scope === \"string\" ? parsed.scope : undefined,\n };\n }\n\n private async formPost(path: string, body: Record<string, string>): Promise<Response> {\n const url = `${this.baseUrl}${path}`;\n const params = new URLSearchParams({\n ...body,\n client_id: this.clientId,\n client_secret: this.clientSecret,\n });\n const ctrl = new AbortController();\n const t = setTimeout(() => ctrl.abort(), this.timeoutMs);\n let res: Response;\n try {\n res = await this.fetchImpl(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Accept: \"application/json\",\n },\n body: params.toString(),\n signal: ctrl.signal,\n });\n } catch (err) {\n if (ctrl.signal.aborted) {\n throw new MantyxNetworkError(`OAuth request timed out after ${this.timeoutMs}ms`);\n }\n throw new MantyxNetworkError(`OAuth network error: ${(err as Error).message}`, {\n cause: err,\n });\n } finally {\n clearTimeout(t);\n }\n if (!res.ok) {\n let errBody: { error?: unknown; error_description?: unknown } = {};\n try {\n errBody = (await res.json()) as typeof errBody;\n } catch {\n // ignore\n }\n const oauthError = typeof errBody.error === \"string\" ? errBody.error : `http_${res.status}`;\n const desc =\n typeof errBody.error_description === \"string\" ? errBody.error_description : undefined;\n throw new MantyxOAuthError(oauthError, desc, res.status);\n }\n return res;\n }\n}\n\n// -------------------------------------------------------------- internals\n\ninterface TokenCache {\n token: OAuthToken | undefined;\n inflight: Promise<OAuthToken> | null;\n}\n\n/**\n * Wrap a `mintToken` thunk into a single-flight {@link TokenSource}\n * with a cache + proactive-refresh skew. The cache is overwritten\n * atomically on every successful mint; the in-flight promise\n * collapses N concurrent expired-token observers into one mint call.\n *\n * Single-flight is an efficiency, not a correctness requirement —\n * `docs/oauth.md` explicitly allows multiple concurrent refreshes\n * against the same refresh token — but it keeps the token-endpoint\n * QPS reasonable when an SDK consumer fans out work in parallel.\n */\nfunction makeTokenSource(\n cache: TokenCache,\n skewMs: number,\n mint: () => Promise<OAuthToken>,\n): TokenSource {\n return async (reason: TokenRequestReason = \"initial\"): Promise<string> => {\n if (reason !== \"unauthorized\" && cache.token && !isExpiring(cache.token, skewMs)) {\n return cache.token.accessToken;\n }\n if (cache.inflight) {\n const t = await cache.inflight;\n if (reason === \"unauthorized\" && t === cache.token) {\n // If the inflight refresh was triggered by a benign cache miss\n // and we observed an unauthorized hint after it started, fall\n // through and mint again so the caller never gets a stale token.\n } else {\n return t.accessToken;\n }\n }\n cache.inflight = mint().then(\n (t) => {\n cache.token = t;\n return t;\n },\n (err: unknown) => {\n throw err;\n },\n );\n try {\n const t = await cache.inflight;\n return t.accessToken;\n } finally {\n cache.inflight = null;\n }\n };\n}\n\nfunction isExpiring(token: OAuthToken, skewMs: number): boolean {\n return token.expiresAt - Date.now() <= skewMs;\n}\n\nfunction normalizeScope(scope: string | readonly string[] | undefined): string | undefined {\n if (scope === undefined) return undefined;\n if (typeof scope === \"string\") {\n const trimmed = scope.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n }\n const joined = scope.filter((s) => typeof s === \"string\" && s.length > 0).join(\" \");\n return joined.length > 0 ? joined : undefined;\n}\n","/**\n * Release version — synced from repo root VERSION (`npm run sync-version`).\n */\nexport const SDK_VERSION = \"0.12.0\";\n"],"mappings":"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","names":[]}
|
|
@@ -361,8 +361,12 @@ The agent spec is the body shape used by `POST /agent-runs` and `POST
|
|
|
361
361
|
"hive_consult_ontology": { "maxCalls": 4 },
|
|
362
362
|
"scary_tool": { "maxCalls": 0 },
|
|
363
363
|
},
|
|
364
|
+
"supervisor": {
|
|
365
|
+
// optional, see §4.8 — platform LLM judge; pass false to disable
|
|
366
|
+
"interval": 5,
|
|
367
|
+
},
|
|
364
368
|
"metadata": {
|
|
365
|
-
// optional, see §4.
|
|
369
|
+
// optional, see §4.9
|
|
366
370
|
"customer": "acme",
|
|
367
371
|
"env": "prod",
|
|
368
372
|
},
|
|
@@ -844,7 +848,64 @@ during normal multi-entity reads. The loop-detection guard catches the
|
|
|
844
848
|
pathological "same `(name, args)` batch over and over" case for that
|
|
845
849
|
family without needing per-tool caps.
|
|
846
850
|
|
|
847
|
-
### 4.8 `
|
|
851
|
+
### 4.8 `supervisor` (run judge)
|
|
852
|
+
|
|
853
|
+
`supervisor` controls the optional **run supervisor** — an LLM judge that
|
|
854
|
+
periodically reviews the agent's transcript (reasoning, tool calls, tool
|
|
855
|
+
results, visible text) and may steer the run:
|
|
856
|
+
|
|
857
|
+
- **`on_track`** — no-op; the run continues.
|
|
858
|
+
- **`redirect`** — a steering user message is injected; tools stay available.
|
|
859
|
+
- **`finalize`** — the next turn is forced tools-disabled so the run lands a
|
|
860
|
+
clean final answer.
|
|
861
|
+
|
|
862
|
+
Reviews fire every **`interval` LLM calls** (`completeTurn` invocations) at
|
|
863
|
+
the bottom of tool-emitting rounds. Default interval is **5** when enabled.
|
|
864
|
+
|
|
865
|
+
```jsonc
|
|
866
|
+
"supervisor": {
|
|
867
|
+
"interval": 5 // optional — LLM calls between reviews; default 5
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// or:
|
|
871
|
+
"supervisor": false // explicitly disable the platform judge for this run
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
| Field | Type | Required | Notes |
|
|
875
|
+
| ----------------- | --------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
876
|
+
| `interval` | integer ≥ 1 | no | Defaults to **5** when the supervisor is enabled and `interval` is omitted. Capped at **100** server-side. |
|
|
877
|
+
| (literal `false`) | `false` | no | Disables the run supervisor for this run. `loopDetection` and `toolBudgets` still apply. |
|
|
878
|
+
|
|
879
|
+
**Defaults.** When `supervisor` is **omitted**, MANTYX enables the platform
|
|
880
|
+
LLM judge on ephemeral runs. Pass `"supervisor": false` to opt out.
|
|
881
|
+
|
|
882
|
+
**SDK-only usage.** When calling `@mantyx/ts-sdk` directly (not via
|
|
883
|
+
`POST /agent-runs`), the supervisor is **off unless explicitly configured**:
|
|
884
|
+
pass `supervisor: { review, interval? }` on `RunAgentOptions` to enable a
|
|
885
|
+
caller-supplied judge, or pass `supervisor: false` (or omit the field) to
|
|
886
|
+
keep it disabled. The wire field above controls the **platform-hosted** judge
|
|
887
|
+
on API/ephemeral runs only.
|
|
888
|
+
|
|
889
|
+
Validation (server-side, `400 invalid_request` on violation):
|
|
890
|
+
|
|
891
|
+
| Constraint | Limit |
|
|
892
|
+
| ----------------- | ----- |
|
|
893
|
+
| `interval` upper bound | `100` |
|
|
894
|
+
|
|
895
|
+
**Inheritance for sessions.**
|
|
896
|
+
|
|
897
|
+
- `POST /agent-sessions { supervisor }` — sets the session-default, applied
|
|
898
|
+
to every subsequent message run.
|
|
899
|
+
- `POST /agent-sessions/:id/messages { supervisor }` — optional per-message
|
|
900
|
+
override; applies to that one run only and does not mutate the session's
|
|
901
|
+
stored value.
|
|
902
|
+
|
|
903
|
+
**Observability.** Each review emits a SSE `supervisor` event (see §7) —
|
|
904
|
+
including `on_track` checks — so SDK clients can render supervisor activity.
|
|
905
|
+
When `action` is `redirect` or `finalize`, the pipeline has already applied
|
|
906
|
+
the verdict by the time the event arrives.
|
|
907
|
+
|
|
908
|
+
### 4.9 `metadata` (developer-supplied KV for filtering)
|
|
848
909
|
|
|
849
910
|
`metadata` is a flat string→string KV that is **persisted alongside the run /
|
|
850
911
|
session** and surfaced in the MANTYX dashboard. Use it to tag runs with your
|
|
@@ -962,9 +1023,6 @@ data: <utf-8 JSON>
|
|
|
962
1023
|
`<type>` and `<data>` shapes:
|
|
963
1024
|
|
|
964
1025
|
```jsonc
|
|
965
|
-
// running message
|
|
966
|
-
{ "seq": 1, "type": "started", "data": {} }
|
|
967
|
-
|
|
968
1026
|
// streamed assistant tokens (zero or more per turn)
|
|
969
1027
|
{ "seq": 2, "type": "assistant_delta", "data": { "text": "Hello" } }
|
|
970
1028
|
|
|
@@ -1001,6 +1059,11 @@ data: <utf-8 JSON>
|
|
|
1001
1059
|
// is observability so SDK clients can render "memory budget exhausted" status notes.
|
|
1002
1060
|
{ "seq": 7, "type": "tool_budget_exceeded", "data": { "tool": "recall", "maxCalls": 4, "callIndex": 5 } }
|
|
1003
1061
|
|
|
1062
|
+
// run-supervisor check (see §4.8). Fired on every review — on_track included.
|
|
1063
|
+
{ "seq": 7, "type": "supervisor", "data": { "action": "on_track", "reason": "Agent is making progress.", "llmCalls": 5 } }
|
|
1064
|
+
{ "seq": 8, "type": "supervisor", "data": { "action": "redirect", "reason": "Stuck re-querying.", "redirect": "Answer from the data you already have.", "llmCalls": 10 } }
|
|
1065
|
+
{ "seq": 9, "type": "supervisor", "data": { "action": "finalize", "reason": "Enough to answer.", "llmCalls": 15 } }
|
|
1066
|
+
|
|
1004
1067
|
// terminal event
|
|
1005
1068
|
// Every terminal `result` event also carries `tokens`, `turns`, and `model`
|
|
1006
1069
|
// for cost attribution and dashboards — see §7.1. Older platforms (pre-
|
|
@@ -1241,18 +1304,18 @@ A reference SDK should:
|
|
|
1241
1304
|
- Treat `thinking_delta` events as opt-in callback fodder; many UIs hide
|
|
1242
1305
|
them by default. Their presence depends on `reasoningLevel > 0` and
|
|
1243
1306
|
on the active model exposing thought parts.
|
|
1244
|
-
- Accept `loopDetection` and `
|
|
1245
|
-
them through unchanged (see §4.6 / §4.7).
|
|
1246
|
-
omitting them keeps MANTYX's runtime defaults; passing
|
|
1247
|
-
`loopDetection: false`
|
|
1248
|
-
defaults; passing entries layers caller
|
|
1249
|
-
defaults.
|
|
1250
|
-
- Treat `loop_detected` and `
|
|
1251
|
-
observability-only — the server already substituted
|
|
1252
|
-
tool-results / steering nudges
|
|
1253
|
-
the event to the caller (status banner,
|
|
1254
|
-
**not** abort the run on these events; the run
|
|
1255
|
-
`result` / `error` / `cancelled` as usual.
|
|
1307
|
+
- Accept `loopDetection`, `toolBudgets`, and `supervisor` from the caller
|
|
1308
|
+
and pass them through unchanged (see §4.6 / §4.7 / §4.8). All three are
|
|
1309
|
+
_additive_: omitting them keeps MANTYX's runtime defaults; passing
|
|
1310
|
+
`loopDetection: false` or `supervisor: false` opts out; passing
|
|
1311
|
+
`toolBudgets: {}` clears the defaults; passing entries layers caller
|
|
1312
|
+
overrides on top of the defaults.
|
|
1313
|
+
- Treat `loop_detected`, `tool_budget_exceeded`, and `supervisor` SSE
|
|
1314
|
+
events as observability-only — the server already substituted synthetic
|
|
1315
|
+
tool-results / steering nudges / supervisor verdicts where applicable, so
|
|
1316
|
+
the SDK's job is just to surface the event to the caller (status banner,
|
|
1317
|
+
log line, telemetry). Do **not** abort the run on these events; the run
|
|
1318
|
+
continues through `result` / `error` / `cancelled` as usual.
|
|
1256
1319
|
- On terminal `result`, resolve the call. On `error` subtype, throw.
|
|
1257
1320
|
4. Re-emit assistant deltas/events as a stream/iterator for callers who care
|
|
1258
1321
|
about live output.
|
|
@@ -1269,4 +1332,4 @@ A reference SDK should:
|
|
|
1269
1332
|
|
|
1270
1333
|
The npm package [`@mantyx/sdk`](https://www.npmjs.com/package/@mantyx/sdk) and the Go module
|
|
1271
1334
|
[`github.com/mantyx/mantyx-go-sdk`](https://github.com/mantyx/mantyx-go-sdk) are reference implementations of this protocol
|
|
1272
|
-
(maintained in the official **mantyx-sdk** repositories).
|
|
1335
|
+
(maintained in the official **mantyx-sdk** repositories).
|
package/docs/wire-protocol.md
CHANGED
|
@@ -132,6 +132,10 @@ short-circuit, etc.) see `agent-runs-protocol.md` §4.
|
|
|
132
132
|
"recall": { "maxCalls": 4 },
|
|
133
133
|
"hive_consult_ontology": { "maxCalls": 4 },
|
|
134
134
|
},
|
|
135
|
+
"supervisor": {
|
|
136
|
+
// optional; see §8.4 — platform LLM judge on ephemeral runs
|
|
137
|
+
"interval": 5,
|
|
138
|
+
},
|
|
135
139
|
"metadata": { "customer": "acme" }, // optional, free-form k/v
|
|
136
140
|
}
|
|
137
141
|
```
|
|
@@ -140,9 +144,9 @@ short-circuit, etc.) see `agent-runs-protocol.md` §4.
|
|
|
140
144
|
|
|
141
145
|
Same body shape, posted to `POST /agent-sessions/:id/messages`. The session
|
|
142
146
|
keeps the conversation history; per-message `tools`, `reasoningLevel`,
|
|
143
|
-
`outputSchema`, `loopDetection`, and `
|
|
144
|
-
defaults for that single run only — the next run falls back to
|
|
145
|
-
the session was created with.
|
|
147
|
+
`outputSchema`, `loopDetection`, `toolBudgets`, and `supervisor` _replace_
|
|
148
|
+
the session's defaults for that single run only — the next run falls back to
|
|
149
|
+
whatever the session was created with.
|
|
146
150
|
|
|
147
151
|
---
|
|
148
152
|
|
|
@@ -391,6 +395,7 @@ The vocabulary (`EphemeralEventType` in `bus.ts`):
|
|
|
391
395
|
| `local_tool_result_in` | M → SDK | Per client-resolved tool call | Informational mirror of the tool-result the SDK just posted, persisted for observability. Re-emitted to late subscribers so they can replay the conversation. |
|
|
392
396
|
| `loop_detected` | M → SDK | 0–2× per run (soft nudge + optional hard cutoff) | Observability for the loop-detection guard (see §8). The server already substituted the synthetic skip + steering nudge — SDK clients render a status note (`looping — nudged` / `looping — gave up`) and otherwise leave the run alone. |
|
|
393
397
|
| `tool_budget_exceeded` | M → SDK | Per intercepted tool call | Observability for per-tool call budgets (see §8). The synthetic `tool_result` carrying the "budget exceeded — pivot or finalize" body lands on the normal tool-result channel; this event is purely so SDK clients can surface a UI banner. |
|
|
398
|
+
| `supervisor` | M → SDK | 0–N× per run (every `interval` LLM calls) | Run-supervisor check (see §4.7 / §8.4). Fired on **every** review — including `on_track` — so SDK clients can render supervisor activity. When the judge steers the run (`redirect` / `finalize`), the pipeline has already injected the steering message or forced a tools-disabled finalize turn. |
|
|
394
399
|
| `assistant_message` | M → SDK | 1× per turn | Final assistant message for the turn (concatenated, persistence-ready). |
|
|
395
400
|
| `result` | M → SDK | 1× terminal | Successful completion. Carries the final assistant text and run summary. |
|
|
396
401
|
| `error` | M → SDK | 1× terminal | Failure. Carries `error` (message), `code` / `errorClass` (category), `finishReason`, and an optional `partialText` salvage payload. See §4.7. |
|
|
@@ -640,7 +645,45 @@ re-parsing tool-result bodies.
|
|
|
640
645
|
|
|
641
646
|
See §8 for the wire-spec field that defines budgets.
|
|
642
647
|
|
|
643
|
-
### 4.7
|
|
648
|
+
### 4.7 `supervisor`
|
|
649
|
+
|
|
650
|
+
```jsonc
|
|
651
|
+
// on_track — the judge reviewed the run and decided not to intervene
|
|
652
|
+
{ "seq": 15, "type": "supervisor",
|
|
653
|
+
"data": { "action": "on_track", "reason": "Agent is gathering context via search before answering.", "llmCalls": 5 } }
|
|
654
|
+
|
|
655
|
+
// redirect — a steering user message was injected; the agent keeps its tools
|
|
656
|
+
{ "seq": 20, "type": "supervisor",
|
|
657
|
+
"data": { "action": "redirect", "reason": "Repeating the same search with identical args.", "redirect": "Stop re-querying; synthesize an answer from the results you already have.", "llmCalls": 10 } }
|
|
658
|
+
|
|
659
|
+
// finalize — the run was forced to wrap up on a tools-disabled turn
|
|
660
|
+
{ "seq": 25, "type": "supervisor",
|
|
661
|
+
"data": { "action": "finalize", "reason": "Enough evidence to answer; further tool use is unlikely to help.", "llmCalls": 15 } }
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
| Field | Type | Notes |
|
|
665
|
+
| ---------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
666
|
+
| `action` | string | One of `"on_track"`, `"redirect"`, `"finalize"`. |
|
|
667
|
+
| `reason` | string | One- or two-sentence explanation from the judge. |
|
|
668
|
+
| `redirect` | string | Present when `action === "redirect"`: the steering message injected into the conversation (same text the agent sees as a user message). Omitted for `on_track` / `finalize`. |
|
|
669
|
+
| `llmCalls` | integer | Number of LLM calls (`completeTurn` invocations) completed when this review fired. Matches the pipeline's `modelInvocations` counter at the check boundary. |
|
|
670
|
+
|
|
671
|
+
Observability for the run-supervisor guard (see §8.4). The event fires on
|
|
672
|
+
**every** check, not only when the judge intervenes — `on_track` reviews are
|
|
673
|
+
included so SDK clients can show "supervisor reviewed" activity without
|
|
674
|
+
inferring it from missing events.
|
|
675
|
+
|
|
676
|
+
When `action` is `redirect` or `finalize`, the pipeline has already applied
|
|
677
|
+
the verdict by the time this event arrives: a steering user message was
|
|
678
|
+
appended (`redirect`) or the next turn was forced tools-disabled
|
|
679
|
+
(`finalize`). SDK clients should render a status note and **not** try to
|
|
680
|
+
steer the run themselves.
|
|
681
|
+
|
|
682
|
+
Pass `"supervisor": false` in the spec (§8.4) to disable the platform judge
|
|
683
|
+
for a run. Omission keeps the runtime default (supervisor **enabled** on
|
|
684
|
+
ephemeral runs).
|
|
685
|
+
|
|
686
|
+
### 4.8 Terminal events
|
|
644
687
|
|
|
645
688
|
```jsonc
|
|
646
689
|
// Every terminal `result` and `error` event also carries `tokens`, `turns`,
|
|
@@ -692,7 +735,7 @@ SDK can re-fetch via `GET /agent-runs/:runId` will have:
|
|
|
692
735
|
| `error` | Same string as `data.error`. |
|
|
693
736
|
| `failureReason` | `{ "errorClass": "truncation", "finishReason": "max_tokens" }` (JSON object, future-proof for additional triage fields). |
|
|
694
737
|
|
|
695
|
-
### 4.
|
|
738
|
+
### 4.8.1 Cost-attribution fields (`tokens`, `turns`, `model`)
|
|
696
739
|
|
|
697
740
|
Every terminal `result` and `error` event carries three additional
|
|
698
741
|
fields so callers can drive cost dashboards, per-turn budgets, and
|
|
@@ -1030,20 +1073,67 @@ banners without re-parsing tool-result bodies:
|
|
|
1030
1073
|
- `loop_detected` — fired on the soft nudge and again on the hard cutoff
|
|
1031
1074
|
if reached. See §4.5.
|
|
1032
1075
|
- `tool_budget_exceeded` — fired each time a call is intercepted. See §4.6.
|
|
1076
|
+
- `supervisor` — fired on every run-supervisor review (`on_track`,
|
|
1077
|
+
`redirect`, or `finalize`). See §4.7.
|
|
1078
|
+
|
|
1079
|
+
Both guard events (`loop_detected`, `tool_budget_exceeded`) are
|
|
1080
|
+
observability-only: the server has already substituted the synthetic
|
|
1081
|
+
tool-result / steering nudge by the time the SDK sees the event. The
|
|
1082
|
+
`supervisor` event is also observability-only when `action` is
|
|
1083
|
+
`redirect` / `finalize` — the pipeline already applied the verdict. The
|
|
1084
|
+
run continues to its terminal `result` / `error` / `cancelled` as usual.
|
|
1085
|
+
|
|
1086
|
+
### 8.4 `supervisor` (run judge)
|
|
1087
|
+
|
|
1088
|
+
An optional LLM **run supervisor** periodically reviews the agent's
|
|
1089
|
+
transcript (reasoning, tool calls, tool results, visible text) and may
|
|
1090
|
+
steer the run:
|
|
1091
|
+
|
|
1092
|
+
| Verdict | Server action |
|
|
1093
|
+
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1094
|
+
| `on_track` | No-op — the run continues unchanged. |
|
|
1095
|
+
| `redirect` | A steering **user message** is injected; tools stay available on the next turn. |
|
|
1096
|
+
| `finalize` | The next turn is forced **tools-disabled** so the run lands a clean final answer (optionally prefaced by the supervisor's message). |
|
|
1097
|
+
|
|
1098
|
+
Reviews fire every **`interval` LLM calls** (`completeTurn` invocations),
|
|
1099
|
+
measured at the bottom of tool-emitting rounds. Default interval is **5**
|
|
1100
|
+
when the field is omitted.
|
|
1101
|
+
|
|
1102
|
+
```jsonc
|
|
1103
|
+
"supervisor": {
|
|
1104
|
+
"interval": 5 // optional — LLM calls between reviews; default 5
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
// or:
|
|
1108
|
+
"supervisor": false // explicitly disable the platform judge for this run
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
| Field | Type | Notes |
|
|
1112
|
+
| ---------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
1113
|
+
| `interval` | integer ≥ 1 | Optional. Default **5** when omitted. Capped at **100** server-side. |
|
|
1114
|
+
| (literal `false`) | `false` | Disables the run supervisor for this run. Loop detection and tool budgets still apply. |
|
|
1115
|
+
|
|
1116
|
+
**Defaults.** When `supervisor` is **omitted**, MANTYX enables the platform
|
|
1117
|
+
LLM judge on ephemeral runs (web chat enables it separately via the chat
|
|
1118
|
+
runner). Pass `"supervisor": false` to opt out.
|
|
1119
|
+
|
|
1120
|
+
**SDK-only runs.** When a caller uses `@mantyx/ts-sdk` directly (not via
|
|
1121
|
+
`POST /agent-runs`), the supervisor is **off unless explicitly configured**:
|
|
1122
|
+
pass a `RunAgentSupervisor` object with a `review` callback to enable it, or
|
|
1123
|
+
pass `supervisor: false` (or omit the field) to keep it disabled. The wire
|
|
1124
|
+
field above controls the **platform-hosted** judge on ephemeral API runs only.
|
|
1033
1125
|
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
event. The run continues to its terminal `result` / `error` / `cancelled`
|
|
1037
|
-
as usual.
|
|
1126
|
+
Each review emits a SSE `supervisor` event (§4.7). Supervisor LLM usage is
|
|
1127
|
+
recorded under the `supervisor` usage surface for cost attribution.
|
|
1038
1128
|
|
|
1039
|
-
### 8.
|
|
1129
|
+
### 8.5 Session inheritance
|
|
1040
1130
|
|
|
1041
|
-
Like `reasoningLevel` and `outputSchema`,
|
|
1131
|
+
Like `reasoningLevel` and `outputSchema`, the run-guard fields support
|
|
1042
1132
|
session-default + per-message override:
|
|
1043
1133
|
|
|
1044
|
-
- `POST /agent-sessions { loopDetection, toolBudgets }` — sets the
|
|
1134
|
+
- `POST /agent-sessions { loopDetection, toolBudgets, supervisor }` — sets the
|
|
1045
1135
|
session-default applied to every subsequent message run.
|
|
1046
|
-
- `POST /agent-sessions/:id/messages { loopDetection, toolBudgets }` —
|
|
1136
|
+
- `POST /agent-sessions/:id/messages { loopDetection, toolBudgets, supervisor }` —
|
|
1047
1137
|
optional per-message override. Applies to that one run only and does
|
|
1048
1138
|
not mutate the session's stored value.
|
|
1049
1139
|
|
|
@@ -1215,17 +1305,17 @@ A reference SDK should:
|
|
|
1215
1305
|
source-of-truth schema (Zod / Pydantic / etc.) — the server enforces
|
|
1216
1306
|
JSON shape via the provider, but transient model errors can still
|
|
1217
1307
|
produce strings that fail to parse in rare cases.
|
|
1218
|
-
- [ ] Accept `loopDetection` and `
|
|
1219
|
-
them through unchanged (see §8).
|
|
1220
|
-
them keeps the runtime defaults; passing `loopDetection: false`
|
|
1221
|
-
out; passing `toolBudgets: {}` clears the
|
|
1222
|
-
layers caller overrides on top of the defaults.
|
|
1223
|
-
to vendor-specific knobs.
|
|
1224
|
-
- [ ] Treat `loop_detected` and `
|
|
1225
|
-
observability-only (see §4.5 / §4.6). Surface them as
|
|
1226
|
-
/ log lines / telemetry — the server already substituted
|
|
1227
|
-
synthetic tool-results / steering nudges, so the
|
|
1228
|
-
consuming the stream until the terminal event lands.
|
|
1308
|
+
- [ ] Accept `loopDetection`, `toolBudgets`, and `supervisor` from the caller
|
|
1309
|
+
and pass them through unchanged (see §8). All three are _additive_ —
|
|
1310
|
+
omitting them keeps the runtime defaults; passing `loopDetection: false`
|
|
1311
|
+
or `supervisor: false` opts out; passing `toolBudgets: {}` clears the
|
|
1312
|
+
defaults; passing entries layers caller overrides on top of the defaults.
|
|
1313
|
+
Do **not** translate to vendor-specific knobs.
|
|
1314
|
+
- [ ] Treat `loop_detected`, `tool_budget_exceeded`, and `supervisor` SSE
|
|
1315
|
+
events as observability-only (see §4.5 / §4.6 / §4.7). Surface them as
|
|
1316
|
+
status notes / log lines / telemetry — the server already substituted
|
|
1317
|
+
synthetic tool-results / steering nudges / supervisor verdicts, so the
|
|
1318
|
+
SDK should keep consuming the stream until the terminal event lands.
|
|
1229
1319
|
- [ ] Maintain three local-callback registries (or one tagged-union
|
|
1230
1320
|
registry), keyed by `name`: - generic local tools (`kind: "local"`), - local A2A peers (`kind: "a2a_local"`, indexed by some Agent Card
|
|
1231
1321
|
field — typically `agentCard.url`), - local MCP servers (`kind: "mcp_local"`, indexed by the SDK-side
|
|
@@ -1262,4 +1352,4 @@ A reference SDK should:
|
|
|
1262
1352
|
- [A2A spec](https://google.github.io/A2A/specification/) — canonical
|
|
1263
1353
|
Agent Card schema.
|
|
1264
1354
|
- [MCP spec](https://spec.modelcontextprotocol.io/) — canonical `Tool` and
|
|
1265
|
-
`Implementation` shapes.
|
|
1355
|
+
`Implementation` shapes.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantyx/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "MANTYX as a hosted agent runtime: define ephemeral agents, mix server-side MANTYX tools with locally-executed tools, run them remotely.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|