@desplega.ai/agent-swarm 1.113.0 → 1.115.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/README.md +2 -2
- package/dist/{actions-q4n7cz6e.js → actions-17ya5wde.js} +8 -9
- package/dist/{app-es4nzc71.js → app-4f718yk0.js} +6 -6
- package/dist/{assistant-0x8ey0vs.js → assistant-ah7csnjz.js} +19 -18
- package/dist/{boot-reembed-69agkhv6.js → boot-reembed-4jpxhb25.js} +6 -6
- package/dist/{boot-reembed-p0ny05t8.js → boot-reembed-qata34xa.js} +5 -5
- package/dist/{boot-scrub-logs-yybrkmvr.js → boot-scrub-logs-tv48nn3z.js} +4 -4
- package/dist/{claude-adapter-mma9a1yr.js → claude-adapter-3csn5bks.js} +3 -3
- package/dist/{claude-managed-adapter-pv2nbqy4.js → claude-managed-adapter-kysmkz89.js} +2 -2
- package/dist/cli-0pygq9r1.js +429 -0
- package/dist/{cli-hsetkkd3.js → cli-12vb0xg6.js} +33 -9
- package/dist/{cli-ncfgsqbd.js → cli-15y7qhzp.js} +262 -33
- package/dist/{cli-7ga43spr.js → cli-1yjrbg1f.js} +1 -1
- package/dist/{cli-36ymmpmp.js → cli-3samkyap.js} +2 -2
- package/dist/cli-52fg7s7x.js +269 -0
- package/dist/{cli-hjhvekf4.js → cli-65ekje01.js} +11 -8
- package/dist/{cli-h3k622d0.js → cli-6aq8xhvm.js} +90 -3
- package/dist/{cli-f3qa069v.js → cli-7xje5amz.js} +1 -1
- package/dist/cli-86fdnerz.js +405 -0
- package/dist/{cli-9cy6nc5m.js → cli-8fjwym53.js} +1 -1
- package/dist/{cli-c3frqf65.js → cli-b23axgz4.js} +3 -3
- package/dist/{cli-a0t7vapf.js → cli-bc89my0z.js} +1 -1
- package/dist/{cli-bnzbn8j8.js → cli-beea4a3y.js} +73 -24
- package/dist/{cli-q21d49ac.js → cli-bw80ck94.js} +1 -1
- package/dist/{cli-pwc5e83c.js → cli-cchksf5q.js} +12 -7
- package/dist/{cli-30bbaveh.js → cli-cmw9rzy5.js} +2 -2
- package/dist/{cli-y6mrptcn.js → cli-cnwssmsr.js} +1 -1
- package/dist/{cli-r5g6ngtn.js → cli-dcca6j8m.js} +46 -5
- package/dist/{cli-tkmm5nf9.js → cli-e61fs2hp.js} +59 -2
- package/dist/{cli-6wscj9z0.js → cli-e7306swv.js} +14 -14
- package/dist/{cli-dcfyyh3t.js → cli-ftyf58xj.js} +2 -2
- package/dist/{cli-4xyj9jtq.js → cli-hrhye2rm.js} +1062 -478
- package/dist/cli-jkhwmmgg.js +136 -0
- package/dist/{cli-y4ycrnjc.js → cli-k3y4z3h5.js} +1 -1
- package/dist/{cli-6xd0bvf8.js → cli-ma04faw5.js} +1 -1
- package/dist/{cli-2fc97xm5.js → cli-mnbgv3w7.js} +4 -4
- package/dist/{cli-trmapn9k.js → cli-pet3aev6.js} +95 -10
- package/dist/{cli-z46pnksz.js → cli-q1rx7g0d.js} +175 -28
- package/dist/{cli-qwgtacd6.js → cli-r9f6ngrf.js} +1244 -336
- package/dist/{cli-gtkqc144.js → cli-shvaxw27.js} +13 -7
- package/dist/{cli-ywez6yps.js → cli-sq7f72d6.js} +1 -1
- package/dist/cli-t0vqevnm.js +393 -0
- package/dist/{cli-tbw13sx8.js → cli-tece6zgm.js} +1 -1
- package/dist/{cli-bgvskydy.js → cli-tffvrh6j.js} +1 -1
- package/dist/cli-wh91zby6.js +187222 -0
- package/dist/{cli-anrj584m.js → cli-xz9aq0rf.js} +1 -1
- package/dist/{cli-mq580e06.js → cli-zsrdty74.js} +18 -18
- package/dist/cli.js +32 -15
- package/dist/{codex-adapter-5cmy858w.js → codex-adapter-de3w0nch.js} +10 -10
- package/dist/{codex-session-runner-46q8mrba.js → codex-session-runner-bnw8cysk.js} +10 -10
- package/dist/{commands-z5dbwta3.js → commands-hch4e2k1.js} +4 -4
- package/dist/{db-bbgahh4z.js → db-p3h2j06x.js} +14 -4
- package/dist/{handlers-6b44317z.js → handlers-9er4zx9q.js} +17 -31
- package/dist/{hook-sbp5fmps.js → hook-4axs756z.js} +7 -7
- package/dist/{http-2xz43gz3.js → http-809fvtz2.js} +1934 -886
- package/dist/{index-wz1ea15p.js → index-2dgfj9av.js} +3 -3
- package/dist/{index-nhat35em.js → index-47em3d2n.js} +14 -11
- package/dist/{index-5ghxn8s6.js → index-9fqaktjr.js} +12 -9
- package/dist/{index-0gzmqj4k.js → index-9ht8gr56.js} +13 -10
- package/dist/{index-vfedgdw6.js → index-rm85cfdx.js} +368 -40
- package/dist/{index-ykvs43z4.js → index-t2snazez.js} +4 -4
- package/dist/{keepalive-62gfj91d.js → keepalive-3cps6e3e.js} +7 -6
- package/dist/{lead-ag0akn0v.js → lead-fkk95wgq.js} +36 -32
- package/dist/{maintenance-xxrrzk5x.js → maintenance-fweqh0yj.js} +6 -6
- package/dist/{mistral-conversations-26yrnbf1.js → mistral-conversations-7gqnz2wt.js} +7 -7
- package/dist/oauth-refresh-sweep-sycqtbph.js +114 -0
- package/dist/{onboard-64zyfcbs.js → onboard-cy0rkgvx.js} +2 -2
- package/dist/{opencode-adapter-mqsxr170.js → opencode-adapter-p6bxnbm4.js} +2 -2
- package/dist/{otel-zjd9zaxs.js → otel-g299k41b.js} +2 -2
- package/dist/{otel-impl-b86qseaa.js → otel-impl-wbns8hf7.js} +1 -1
- package/dist/{pi-mono-adapter-kxjxpfgy.js → pi-mono-adapter-05sck1ds.js} +17 -109
- package/dist/{pricing-refresh-cgtkffpa.js → pricing-refresh-0hvazmkf.js} +6 -6
- package/dist/rbac-roles-kfesjy9y.js +28 -0
- package/dist/rbac-roles-mwcwhbxc.js +283 -0
- package/dist/{seed-pricing-1r1y1xkq.js → seed-pricing-h2kdq4cq.js} +5 -5
- package/dist/{setup-mkcgkjf7.js → setup-8506q97w.js} +2 -2
- package/dist/{worker-351sze63.js → worker-4r5c6zyb.js} +36 -32
- package/openapi.json +1327 -20
- package/package.json +3 -3
- package/src/agentmail/handlers.ts +16 -6
- package/src/agentmail/templates.ts +25 -5
- package/src/be/audit-user.ts +28 -4
- package/src/be/db-queries/oauth.ts +42 -0
- package/src/be/db.ts +314 -28
- package/src/be/identity.ts +79 -0
- package/src/be/mcp-proxy.ts +132 -0
- package/src/be/migrations/109_rbac_roles.sql +79 -0
- package/src/be/migrations/111_oauth_credential_bindings.sql +6 -0
- package/src/be/migrations/112_script_connections_graphql.sql +96 -0
- package/src/be/migrations/113_task_routing_affinity.sql +9 -0
- package/src/be/migrations/114_backfill_gpt_5_6_pricing.sql +15 -0
- package/src/be/modelsdev-cache.json +135 -0
- package/src/be/oauth-credential-bindings.ts +35 -0
- package/src/be/oauth-refresh-sweep.ts +159 -0
- package/src/be/rbac-audit.ts +52 -13
- package/src/be/rbac-roles.ts +424 -0
- package/src/be/script-connections.ts +459 -40
- package/src/be/script-credential-broker.ts +29 -3
- package/src/be/scripts/typecheck.ts +51 -7
- package/src/be/seed-scripts/catalog/catalog-report.inline.ts +325 -24
- package/src/be/seed-scripts/catalog/catalog-report.ts +325 -24
- package/src/be/users.ts +24 -0
- package/src/cli.tsx +17 -0
- package/src/github/handlers.ts +10 -9
- package/src/github/templates.ts +45 -9
- package/src/gitlab/handlers.ts +20 -8
- package/src/gitlab/templates.ts +18 -6
- package/src/heartbeat/heartbeat.ts +143 -21
- package/src/http/all-routes.ts +3 -0
- package/src/http/approval-requests.ts +12 -0
- package/src/http/config.ts +44 -10
- package/src/http/core.ts +30 -3
- package/src/http/favorites.ts +1 -5
- package/src/http/index.ts +25 -1
- package/src/http/mcp-oauth.ts +2 -0
- package/src/http/mcp-servers.ts +44 -3
- package/src/http/oauth-generic.ts +105 -0
- package/src/http/poll.ts +20 -3
- package/src/http/script-connection-proxy.ts +116 -0
- package/src/http/script-connections.ts +1657 -0
- package/src/http/scripts.ts +27 -7
- package/src/http/skills.ts +11 -0
- package/src/http/tasks.ts +27 -13
- package/src/http/x.ts +6 -2
- package/src/integrations/kapso/inbound.ts +23 -6
- package/src/jira/sync.ts +111 -17
- package/src/jira/templates.ts +10 -2
- package/src/mcp-client/http-client.ts +194 -0
- package/src/oauth/app-validation.ts +16 -0
- package/src/oauth/ensure-token.ts +69 -14
- package/src/oauth/mcp-wrapper.ts +15 -0
- package/src/oauth/wrapper.ts +48 -20
- package/src/prompts/session-templates.ts +3 -3
- package/src/providers/codex-models.ts +100 -30
- package/src/providers/modelsdev-reasoning.json +51 -0
- package/src/providers/pi-mono-mcp-client.ts +2 -133
- package/src/providers/reasoning-effort.ts +11 -5
- package/src/rbac/admission.ts +68 -0
- package/src/rbac/index.ts +2 -0
- package/src/rbac/legacy-policy.ts +12 -0
- package/src/rbac/permissions.ts +48 -0
- package/src/scheduler/scheduler.ts +9 -1
- package/src/scripts-runtime/api-client.ts +151 -19
- package/src/scripts-runtime/api-types.ts +38 -6
- package/src/scripts-runtime/credential-broker/default-bindings.ts +1 -0
- package/src/scripts-runtime/credential-broker/types.ts +8 -0
- package/src/scripts-runtime/ctx.ts +11 -1
- package/src/scripts-runtime/eval-harness.ts +5 -1
- package/src/scripts-runtime/executors/types.ts +2 -1
- package/src/scripts-runtime/loader.ts +3 -1
- package/src/scripts-runtime/mcp-client.ts +87 -0
- package/src/scripts-runtime/sdk-allowlist.ts +1 -0
- package/src/scripts-runtime/types/stdlib.d.ts +6 -0
- package/src/scripts-runtime/types/swarm-sdk.d.ts +19 -0
- package/src/server-user.ts +114 -59
- package/src/server.ts +12 -0
- package/src/slack/assistant.ts +14 -6
- package/src/slack/enrich.ts +17 -0
- package/src/slack/handlers.ts +8 -22
- package/src/slack/responses.ts +91 -1
- package/src/slack/thread-buffer.ts +6 -3
- package/src/slack/watcher.ts +9 -6
- package/src/tasks/worker-follow-up.ts +100 -3
- package/src/tests/agentmail-handlers.test.ts +32 -3
- package/src/tests/approval-requests.test.ts +61 -0
- package/src/tests/audit-user.test.ts +47 -1
- package/src/tests/codex-adapter.test.ts +46 -23
- package/src/tests/credential-broker.test.ts +5 -2
- package/src/tests/delete-page-tool.test.ts +197 -0
- package/src/tests/github-handlers.test.ts +28 -2
- package/src/tests/gitlab-handlers.test.ts +125 -0
- package/src/tests/heartbeat.test.ts +48 -3
- package/src/tests/identity.test.ts +117 -0
- package/src/tests/jira-sync.test.ts +99 -1
- package/src/tests/kapso-inbound.test.ts +7 -0
- package/src/tests/mcp-tools-user.test.ts +60 -6
- package/src/tests/model-control.test.ts +8 -2
- package/src/tests/oauth-credential-bindings.test.ts +490 -0
- package/src/tests/oauth-refresh-sweep.test.ts +160 -0
- package/src/tests/pool-affinity.test.ts +401 -0
- package/src/tests/prompt-template-remaining.test.ts +2 -2
- package/src/tests/rbac-admission-e2e.test.ts +386 -0
- package/src/tests/rbac-admission.test.ts +492 -0
- package/src/tests/rbac-e2e-helpers.ts +3 -0
- package/src/tests/rbac-engine.test.ts +50 -0
- package/src/tests/rbac-mcp-admission-e2e.test.ts +362 -0
- package/src/tests/rbac-roles.test.ts +328 -0
- package/src/tests/reasoning-effort.test.ts +15 -2
- package/src/tests/schedule-target-type.test.ts +50 -0
- package/src/tests/scheduled-tasks.test.ts +40 -0
- package/src/tests/script-apis-mcp.test.ts +37 -13
- package/src/tests/script-connections-http.test.ts +1158 -0
- package/src/tests/script-connections-mcp.test.ts +894 -0
- package/src/tests/script-connections.test.ts +754 -6
- package/src/tests/scripts-external-api.test.ts +49 -1
- package/src/tests/scripts-runtime-secret-egress.test.ts +1 -0
- package/src/tests/scripts-runtime.test.ts +291 -0
- package/src/tests/seed-scripts.test.ts +36 -0
- package/src/tests/slack-identity-resolution.test.ts +37 -3
- package/src/tests/slack-inline-output.test.ts +65 -0
- package/src/tests/slack-thread-buffer.test.ts +28 -0
- package/src/tests/tool-annotations.test.ts +1 -0
- package/src/tests/user-token-rest-auth.test.ts +140 -1
- package/src/tests/workflow-swarm-script.test.ts +44 -0
- package/src/tests/workflow-triggers-v2.test.ts +300 -1
- package/src/tools/credential-bindings/tool.ts +277 -18
- package/src/tools/delete-page.ts +145 -0
- package/src/tools/mcp-servers/mcp-server-delete.ts +7 -4
- package/src/tools/request-human-input.ts +2 -0
- package/src/tools/resolve-user.ts +77 -27
- package/src/tools/script-apis.ts +62 -3
- package/src/tools/script-connections/tool.ts +275 -31
- package/src/tools/send-task.ts +15 -1
- package/src/tools/task-action.ts +34 -2
- package/src/tools/templates.ts +53 -1
- package/src/tools/tool-config.ts +2 -1
- package/src/tools/utils.ts +2 -0
- package/src/tools/workflows/create-workflow.ts +8 -1
- package/src/tools/workflows/update-workflow.ts +11 -2
- package/src/types.ts +90 -11
- package/src/utils/scoped-resource.ts +25 -0
- package/src/workflows/executors/human-in-the-loop.ts +1 -0
- package/src/workflows/executors/swarm-script.ts +7 -1
- package/src/workflows/triggers.ts +146 -18
- package/templates/skills/swarm-scripts/content.md +33 -8
- package/dist/cli-bgef578c.js +0 -771
- package/dist/cli-c9mtm09b.js +0 -43
- package/dist/cli-ceewqajw.js +0 -1698
- package/dist/{anthropic-messages-8nc6r2dw.js → anthropic-messages-kq3pjhbp.js} +6 -6
- package/dist/{azure-openai-responses-6yfzw3qw.js → azure-openai-responses-gm1ejtmn.js} +7 -7
- package/dist/{cli-vbraamxr.js → cli-6b4wwmm8.js} +9 -9
- package/dist/{cli-2r7s8jhp.js → cli-sznberje.js} +3 -3
- package/dist/{google-generative-ai-vfq6ymz9.js → google-generative-ai-hyrra2jm.js} +3 -3
- package/dist/{google-vertex-mj5y5rfj.js → google-vertex-8bs2pzgn.js} +3 -3
- package/dist/{openai-codex-responses-8h6kvfv8.js → openai-codex-responses-3nkvje79.js} +4 -4
- package/dist/{openai-completions-x6kjnqjd.js → openai-completions-0asftkwg.js} +11 -11
- package/dist/{openai-responses-7jwyz3ds.js → openai-responses-pmatek45.js} +10 -10
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal MCP client for Streamable HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* Performs the initialize handshake, tracks MCP session IDs, parses JSON/SSE
|
|
5
|
+
* responses, discovers tools, and forwards tool calls. The client itself is
|
|
6
|
+
* transport-only: callers provide any auth headers and it never imports DB code.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface McpTool {
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
inputSchema: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface McpToolCallResult {
|
|
16
|
+
content: Array<{ type: string; text?: string }>;
|
|
17
|
+
isError?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type McpJsonRpcError = {
|
|
21
|
+
code?: number;
|
|
22
|
+
message?: string;
|
|
23
|
+
data?: unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type McpToolCallEnvelope =
|
|
27
|
+
| { ok: true; result: McpToolCallResult }
|
|
28
|
+
| { ok: false; error: McpJsonRpcError | string };
|
|
29
|
+
|
|
30
|
+
function jsonRpcErrorMessage(error: McpJsonRpcError | string): string {
|
|
31
|
+
if (typeof error === "string") return error;
|
|
32
|
+
return error.message ?? JSON.stringify(error);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type McpHttpClientOptions = {
|
|
36
|
+
protocolVersion?: string;
|
|
37
|
+
clientInfo?: { name: string; version: string };
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
omitEmptyAuthHeaders?: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export class McpHttpClient {
|
|
43
|
+
private sessionId: string | null = null;
|
|
44
|
+
private nextId = 1;
|
|
45
|
+
/** Additional headers merged into every request (e.g. for installed MCP servers) */
|
|
46
|
+
public customHeaders: Record<string, string> = {};
|
|
47
|
+
/**
|
|
48
|
+
* When true, baseUrl is used as-is for requests (external MCP servers).
|
|
49
|
+
* When false (default), /mcp is appended (swarm convention).
|
|
50
|
+
*/
|
|
51
|
+
public useRawUrl = false;
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
private baseUrl: string,
|
|
55
|
+
private apiKey: string,
|
|
56
|
+
private agentId: string,
|
|
57
|
+
private taskId?: string,
|
|
58
|
+
private options: McpHttpClientOptions = {},
|
|
59
|
+
) {}
|
|
60
|
+
|
|
61
|
+
private async send(body: unknown): Promise<{ data: unknown; headers: Headers }> {
|
|
62
|
+
const headers: Record<string, string> = {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
Accept: "application/json, text/event-stream",
|
|
65
|
+
};
|
|
66
|
+
if (this.apiKey || !this.options.omitEmptyAuthHeaders) {
|
|
67
|
+
headers.Authorization = `Bearer ${this.apiKey}`;
|
|
68
|
+
}
|
|
69
|
+
if (this.agentId || !this.options.omitEmptyAuthHeaders) {
|
|
70
|
+
headers["X-Agent-ID"] = this.agentId;
|
|
71
|
+
}
|
|
72
|
+
Object.assign(headers, this.customHeaders);
|
|
73
|
+
if (this.taskId) {
|
|
74
|
+
headers["X-Source-Task-Id"] = this.taskId;
|
|
75
|
+
}
|
|
76
|
+
if (this.sessionId) {
|
|
77
|
+
headers["mcp-session-id"] = this.sessionId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const controller = this.options.timeoutMs ? new AbortController() : null;
|
|
81
|
+
const timeout = controller
|
|
82
|
+
? setTimeout(() => controller.abort(), this.options.timeoutMs)
|
|
83
|
+
: null;
|
|
84
|
+
const url = this.useRawUrl ? this.baseUrl : `${this.baseUrl}/mcp`;
|
|
85
|
+
try {
|
|
86
|
+
const res = await fetch(url, {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers,
|
|
89
|
+
body: JSON.stringify(body),
|
|
90
|
+
signal: controller?.signal,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
throw new Error(`MCP request failed: ${res.status} ${res.statusText}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Capture session ID from response.
|
|
98
|
+
const sid = res.headers.get("mcp-session-id");
|
|
99
|
+
if (sid) this.sessionId = sid;
|
|
100
|
+
|
|
101
|
+
// Handle SSE responses (extract JSON from event stream).
|
|
102
|
+
const contentType = res.headers.get("content-type") || "";
|
|
103
|
+
if (contentType.includes("text/event-stream")) {
|
|
104
|
+
const text = await res.text();
|
|
105
|
+
const dataLines = text
|
|
106
|
+
.split("\n")
|
|
107
|
+
.filter((line) => line.startsWith("data: "))
|
|
108
|
+
.map((line) => line.slice(6));
|
|
109
|
+
const lastData = dataLines[dataLines.length - 1];
|
|
110
|
+
return { data: lastData ? JSON.parse(lastData) : null, headers: res.headers };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const text = await res.text();
|
|
114
|
+
const data = text.trim() ? JSON.parse(text) : null;
|
|
115
|
+
return { data, headers: res.headers };
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
118
|
+
throw new Error(`MCP request timed out after ${this.options.timeoutMs}ms`);
|
|
119
|
+
}
|
|
120
|
+
throw err;
|
|
121
|
+
} finally {
|
|
122
|
+
if (timeout) clearTimeout(timeout);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Perform MCP initialize + initialized handshake */
|
|
127
|
+
async initialize(): Promise<void> {
|
|
128
|
+
const { data } = await this.send({
|
|
129
|
+
jsonrpc: "2.0",
|
|
130
|
+
id: this.nextId++,
|
|
131
|
+
method: "initialize",
|
|
132
|
+
params: {
|
|
133
|
+
protocolVersion: this.options.protocolVersion ?? "2025-03-26",
|
|
134
|
+
capabilities: {},
|
|
135
|
+
clientInfo: this.options.clientInfo ?? {
|
|
136
|
+
name: "agent-swarm-pi-mono",
|
|
137
|
+
version: "1.0.0",
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (!data || typeof data !== "object" || !("result" in (data as Record<string, unknown>))) {
|
|
143
|
+
throw new Error(`MCP initialize failed: ${JSON.stringify(data)}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Send initialized notification (no response expected for notifications).
|
|
147
|
+
await this.send({
|
|
148
|
+
jsonrpc: "2.0",
|
|
149
|
+
method: "notifications/initialized",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Discover all available MCP tools */
|
|
154
|
+
async listTools(): Promise<McpTool[]> {
|
|
155
|
+
const { data } = await this.send({
|
|
156
|
+
jsonrpc: "2.0",
|
|
157
|
+
id: this.nextId++,
|
|
158
|
+
method: "tools/list",
|
|
159
|
+
params: {},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const result = data as { result?: { tools?: McpTool[] }; error?: McpJsonRpcError | string };
|
|
163
|
+
if (result?.error) {
|
|
164
|
+
throw new Error(`MCP tools/list failed: ${jsonRpcErrorMessage(result.error)}`);
|
|
165
|
+
}
|
|
166
|
+
return result?.result?.tools ?? [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Call an MCP tool and preserve either the JSON-RPC result or error shape. */
|
|
170
|
+
async callToolRaw(name: string, args: Record<string, unknown>): Promise<McpToolCallEnvelope> {
|
|
171
|
+
const { data } = await this.send({
|
|
172
|
+
jsonrpc: "2.0",
|
|
173
|
+
id: this.nextId++,
|
|
174
|
+
method: "tools/call",
|
|
175
|
+
params: { name, arguments: args },
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const response = data as {
|
|
179
|
+
result?: McpToolCallResult;
|
|
180
|
+
error?: McpJsonRpcError | string;
|
|
181
|
+
};
|
|
182
|
+
if (response?.error) return { ok: false, error: response.error };
|
|
183
|
+
return {
|
|
184
|
+
ok: true,
|
|
185
|
+
result: response?.result ?? { content: [{ type: "text", text: "No result" }] },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Call an MCP tool */
|
|
190
|
+
async callTool(name: string, args: Record<string, unknown>): Promise<McpToolCallResult> {
|
|
191
|
+
const envelope = await this.callToolRaw(name, args);
|
|
192
|
+
return envelope.ok ? envelope.result : { content: [{ type: "text", text: "No result" }] };
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { assertUrlSafe, publicEndpointSsrfOptions } from "./mcp-wrapper";
|
|
2
|
+
|
|
3
|
+
export const RESERVED_OAUTH_PROVIDERS = new Set(["linear", "jira"]);
|
|
4
|
+
|
|
5
|
+
export function assertOAuthProviderIsNotReserved(provider: string): void {
|
|
6
|
+
if (!RESERVED_OAUTH_PROVIDERS.has(provider.toLowerCase())) return;
|
|
7
|
+
throw new Error(
|
|
8
|
+
`OAuth provider "${provider}" is reserved for dedicated tracker OAuth flows. Use the Linear/Jira tracker integration flow instead.`,
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function assertOAuthAppUrlsSafe(input: { authorizeUrl: string; tokenUrl: string }): void {
|
|
13
|
+
const options = publicEndpointSsrfOptions();
|
|
14
|
+
assertUrlSafe(input.authorizeUrl, options);
|
|
15
|
+
assertUrlSafe(input.tokenUrl, options);
|
|
16
|
+
}
|
|
@@ -5,35 +5,70 @@ import {
|
|
|
5
5
|
isTokenExpiringSoon,
|
|
6
6
|
releaseOAuthRefreshLock,
|
|
7
7
|
} from "../be/db-queries/oauth";
|
|
8
|
-
import type { OAuthTokens } from "../tracker/types";
|
|
8
|
+
import type { OAuthApp, OAuthTokens } from "../tracker/types";
|
|
9
9
|
import { type OAuthProviderConfig, refreshAccessToken } from "./wrapper";
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
11
|
+
function parseMetadata(metadataJson: string | null | undefined): Record<string, unknown> {
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(metadataJson || "{}");
|
|
14
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
15
|
+
} catch {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stringRecord(value: unknown): Record<string, string> | undefined {
|
|
21
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
22
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, String(item)]));
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
/**
|
|
17
|
-
*
|
|
26
|
+
* Map an oauth_apps row to a provider config. Single source of truth for the
|
|
27
|
+
* token-endpoint behavior knobs stored in metadata (extraParams,
|
|
28
|
+
* tokenAuthStyle, tokenBodyFormat) — used by ensure-token and re-exported by
|
|
29
|
+
* be/oauth-credential-bindings (which can't be imported from here without a
|
|
30
|
+
* cycle).
|
|
18
31
|
*/
|
|
19
|
-
function
|
|
20
|
-
const
|
|
21
|
-
|
|
32
|
+
export function oauthAppRowToProviderConfig(app: OAuthApp): OAuthProviderConfig {
|
|
33
|
+
const metadata = parseMetadata(app.metadata);
|
|
34
|
+
const extraParams =
|
|
35
|
+
stringRecord(metadata.extraParams) ??
|
|
36
|
+
(typeof metadata.actor === "string" ? { actor: metadata.actor } : undefined);
|
|
22
37
|
|
|
23
|
-
const metadata = JSON.parse(app.metadata || "{}");
|
|
24
38
|
return {
|
|
25
|
-
provider,
|
|
39
|
+
provider: app.provider,
|
|
26
40
|
clientId: app.clientId,
|
|
27
41
|
clientSecret: app.clientSecret,
|
|
28
42
|
authorizeUrl: app.authorizeUrl,
|
|
29
43
|
tokenUrl: app.tokenUrl,
|
|
30
44
|
redirectUri: app.redirectUri,
|
|
31
|
-
scopes: app.scopes
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
scopes: app.scopes
|
|
46
|
+
.split(",")
|
|
47
|
+
.map((scope) => scope.trim())
|
|
48
|
+
.filter(Boolean),
|
|
49
|
+
extraParams,
|
|
50
|
+
// Standard OAuth wants space-separated scopes; the wrapper's comma
|
|
51
|
+
// default exists only for Linear, which has its own dedicated flow.
|
|
52
|
+
scopeSeparator: " ",
|
|
53
|
+
...(metadata.tokenAuthStyle === "basic" ? { tokenAuthStyle: "basic" as const } : {}),
|
|
54
|
+
...(metadata.tokenBodyFormat === "json" ? { tokenBodyFormat: "json" as const } : {}),
|
|
55
|
+
requiresRefreshTokenRotation: app.provider === "jira",
|
|
34
56
|
};
|
|
35
57
|
}
|
|
36
58
|
|
|
59
|
+
const refreshLocks = new Map<string, Promise<void>>();
|
|
60
|
+
const REFRESH_LOCK_TTL_MS = 2 * 60 * 1000;
|
|
61
|
+
const REFRESH_LOCK_WAIT_MS = 30 * 1000;
|
|
62
|
+
const REFRESH_LOCK_POLL_MS = 250;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build an OAuthProviderConfig from the oauth_apps table for any provider.
|
|
66
|
+
*/
|
|
67
|
+
function getOAuthConfig(provider: string): OAuthProviderConfig | null {
|
|
68
|
+
const app = getOAuthApp(provider);
|
|
69
|
+
return app ? oauthAppRowToProviderConfig(app) : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
37
72
|
async function withProviderRefreshLock<T>(provider: string, fn: () => Promise<T>): Promise<T> {
|
|
38
73
|
const previous = refreshLocks.get(provider) ?? Promise.resolve();
|
|
39
74
|
let release!: () => void;
|
|
@@ -94,6 +129,26 @@ export async function ensureToken(provider: string, bufferMs?: number): Promise<
|
|
|
94
129
|
}
|
|
95
130
|
}
|
|
96
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Buffer large enough that any stored expiry counts as "expiring soon",
|
|
134
|
+
* turning {@link ensureTokenOrThrow} into an unconditional refresh.
|
|
135
|
+
*/
|
|
136
|
+
const FORCE_REFRESH_BUFFER_MS = 1000 * 60 * 60 * 24 * 365 * 100; // ~100 years
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Force a refresh of the stored token regardless of remaining lifetime,
|
|
140
|
+
* going through the exact same in-process queue + DB refresh-lock path as
|
|
141
|
+
* {@link ensureTokenOrThrow}. Used by the manual refresh endpoint and the
|
|
142
|
+
* background refresh sweep's keep-alive path.
|
|
143
|
+
*
|
|
144
|
+
* Like {@link ensureTokenOrThrow}, stays silent (no throw) when the provider
|
|
145
|
+
* has no app config or no stored refresh token — callers that need those to
|
|
146
|
+
* be errors must pre-check.
|
|
147
|
+
*/
|
|
148
|
+
export async function forceRefreshTokenOrThrow(provider: string): Promise<void> {
|
|
149
|
+
await ensureTokenOrThrow(provider, FORCE_REFRESH_BUFFER_MS);
|
|
150
|
+
}
|
|
151
|
+
|
|
97
152
|
/**
|
|
98
153
|
* Strict variant of {@link ensureToken}: throws on refresh failure of a
|
|
99
154
|
* configured provider so callers (keepalive, alerting) can react.
|
package/src/oauth/mcp-wrapper.ts
CHANGED
|
@@ -96,6 +96,21 @@ export function assertUrlSafe(rawUrl: string, opts: SsrfGuardOptions = {}): URL
|
|
|
96
96
|
return parsed;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export function publicEndpointSsrfOptions(): SsrfGuardOptions {
|
|
100
|
+
// Fail closed: NODE_ENV is typically UNSET in the production image (Bun
|
|
101
|
+
// does not default it), so only an explicit development/test environment —
|
|
102
|
+
// or the explicit override flag — may fetch private/insecure endpoints.
|
|
103
|
+
// Local dev entrypoints (`bun run dev:http` / `start:http`) set
|
|
104
|
+
// NODE_ENV=development.
|
|
105
|
+
const env = process.env.NODE_ENV;
|
|
106
|
+
const dev =
|
|
107
|
+
env === "development" || env === "test" || process.env.ALLOW_PRIVATE_NETWORK_URLS === "true";
|
|
108
|
+
return {
|
|
109
|
+
allowPrivateHosts: dev,
|
|
110
|
+
allowInsecure: dev,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
99
114
|
function defaultSsrfOptions(): SsrfGuardOptions {
|
|
100
115
|
return {
|
|
101
116
|
allowPrivateHosts: process.env.MCP_OAUTH_ALLOW_PRIVATE_HOSTS === "true",
|
package/src/oauth/wrapper.ts
CHANGED
|
@@ -29,6 +29,20 @@ export interface OAuthProviderConfig {
|
|
|
29
29
|
* pre-existing consumer of this wrapper.
|
|
30
30
|
*/
|
|
31
31
|
scopeSeparator?: string;
|
|
32
|
+
/**
|
|
33
|
+
* How client credentials are sent to the token endpoint.
|
|
34
|
+
*
|
|
35
|
+
* - `"body"` (default): `client_id` + `client_secret` as body parameters.
|
|
36
|
+
* - `"basic"`: HTTP Basic `Authorization` header (RFC 6749 §2.3.1) —
|
|
37
|
+
* required by providers like Notion that reject body credentials.
|
|
38
|
+
*/
|
|
39
|
+
tokenAuthStyle?: "body" | "basic";
|
|
40
|
+
/**
|
|
41
|
+
* Token request body encoding. `"form"` (default) sends
|
|
42
|
+
* `application/x-www-form-urlencoded`; `"json"` sends a JSON body
|
|
43
|
+
* (Notion requires JSON).
|
|
44
|
+
*/
|
|
45
|
+
tokenBodyFormat?: "form" | "json";
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
interface PendingState {
|
|
@@ -97,6 +111,30 @@ export async function buildAuthorizationUrl(
|
|
|
97
111
|
* Validates the state against our pending map, calls the token endpoint,
|
|
98
112
|
* and persists tokens via storeOAuthTokens().
|
|
99
113
|
*/
|
|
114
|
+
/**
|
|
115
|
+
* Build headers + body for a token-endpoint request, honoring the provider's
|
|
116
|
+
* client-auth style (body params vs HTTP Basic) and body encoding (form vs JSON).
|
|
117
|
+
*/
|
|
118
|
+
function tokenRequestInit(
|
|
119
|
+
config: OAuthProviderConfig,
|
|
120
|
+
params: Record<string, string>,
|
|
121
|
+
): { headers: Record<string, string>; body: string } {
|
|
122
|
+
const useBasic = config.tokenAuthStyle === "basic";
|
|
123
|
+
const bodyParams = useBasic
|
|
124
|
+
? params
|
|
125
|
+
: { ...params, client_id: config.clientId, client_secret: config.clientSecret };
|
|
126
|
+
const headers: Record<string, string> = {};
|
|
127
|
+
if (useBasic) {
|
|
128
|
+
headers.Authorization = `Basic ${Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64")}`;
|
|
129
|
+
}
|
|
130
|
+
if (config.tokenBodyFormat === "json") {
|
|
131
|
+
headers["Content-Type"] = "application/json";
|
|
132
|
+
return { headers, body: JSON.stringify(bodyParams) };
|
|
133
|
+
}
|
|
134
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
135
|
+
return { headers, body: new URLSearchParams(bodyParams).toString() };
|
|
136
|
+
}
|
|
137
|
+
|
|
100
138
|
export async function exchangeCode(
|
|
101
139
|
config: OAuthProviderConfig,
|
|
102
140
|
code: string,
|
|
@@ -111,19 +149,14 @@ export async function exchangeCode(
|
|
|
111
149
|
const { codeVerifier } = pending;
|
|
112
150
|
|
|
113
151
|
// Build token request manually — Linear doesn't use standard OAuth discovery
|
|
114
|
-
const body = new URLSearchParams({
|
|
115
|
-
grant_type: "authorization_code",
|
|
116
|
-
client_id: config.clientId,
|
|
117
|
-
client_secret: config.clientSecret,
|
|
118
|
-
redirect_uri: config.redirectUri,
|
|
119
|
-
code,
|
|
120
|
-
code_verifier: codeVerifier,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
152
|
const response = await fetch(config.tokenUrl, {
|
|
124
153
|
method: "POST",
|
|
125
|
-
|
|
126
|
-
|
|
154
|
+
...tokenRequestInit(config, {
|
|
155
|
+
grant_type: "authorization_code",
|
|
156
|
+
redirect_uri: config.redirectUri,
|
|
157
|
+
code,
|
|
158
|
+
code_verifier: codeVerifier,
|
|
159
|
+
}),
|
|
127
160
|
});
|
|
128
161
|
|
|
129
162
|
if (!response.ok) {
|
|
@@ -167,17 +200,12 @@ export async function refreshAccessToken(
|
|
|
167
200
|
config: OAuthProviderConfig,
|
|
168
201
|
refreshToken: string,
|
|
169
202
|
): Promise<{ accessToken: string; refreshToken?: string; expiresIn?: number; scope?: string }> {
|
|
170
|
-
const body = new URLSearchParams({
|
|
171
|
-
grant_type: "refresh_token",
|
|
172
|
-
client_id: config.clientId,
|
|
173
|
-
client_secret: config.clientSecret,
|
|
174
|
-
refresh_token: refreshToken,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
203
|
const response = await fetch(config.tokenUrl, {
|
|
178
204
|
method: "POST",
|
|
179
|
-
|
|
180
|
-
|
|
205
|
+
...tokenRequestInit(config, {
|
|
206
|
+
grant_type: "refresh_token",
|
|
207
|
+
refresh_token: refreshToken,
|
|
208
|
+
}),
|
|
181
209
|
});
|
|
182
210
|
|
|
183
211
|
if (!response.ok) {
|
|
@@ -420,9 +420,9 @@ The 5 script tools (\`script-search\`, \`script-run\`, \`script-upsert\`, \`scri
|
|
|
420
420
|
- \`agentId\` IS propagated to scripts via the \`X-Agent-ID\` header.
|
|
421
421
|
- \`taskId\` is NOT propagated to scripts — there is no ambient task context. Pass \`taskId\` explicitly via \`args\` if the script needs to call \`ctx.swarm.task_storeProgress\`.
|
|
422
422
|
- Use \`script-query-types\` to inspect the live \`swarm-sdk.d.ts\` before authoring a complex script.
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
- Registration is lead-only; workers should
|
|
423
|
+
- Script connections: lead-registered \`script_connections\` entries expose typed clients in scripts — \`ctx.api.<slug>.<operationId>(...)\` (OpenAPI), \`ctx.api.<slug>.graphql(query, vars)\` (GraphQL), and \`ctx.mcp.<slug>.<toolName>(args)\` (MCP, proxied server-side). Introspect with \`Object.keys(ctx.api ?? {})\` / \`Object.keys(ctx.mcp ?? {})\`.
|
|
424
|
+
- Connection credentials (config secrets or OAuth tokens with auto-refresh) are injected at egress, so scripts calling an external API should prefer a connection over hand-written \`fetch\` + \`[REDACTED:<KEY>]\` headers — and never paste raw secrets.
|
|
425
|
+
- Registration is lead-only (\`script-connections\` + \`credential-bindings\` tools); workers should hand the lead the spec to register: slug, baseUrl or spec URL, allowedHosts, and auth (config key vs OAuth provider).
|
|
426
426
|
`,
|
|
427
427
|
variables: [],
|
|
428
428
|
category: "system",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Codex API-addressable models, verified from https://developers.openai.com/
|
|
3
|
-
* and https://developers.openai.com/api/docs/deprecations as of 2026-
|
|
2
|
+
* Codex API-addressable models, verified from https://developers.openai.com/api/docs/models
|
|
3
|
+
* and https://developers.openai.com/api/docs/deprecations as of 2026-07-10.
|
|
4
4
|
*
|
|
5
5
|
* NOTE: `gpt-5.3-codex-spark` is intentionally excluded. It is a ChatGPT Pro
|
|
6
6
|
* research preview and is NOT API-addressable via the Codex SDK at launch.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* adapter so the onboarding UI and model selector can import it without
|
|
11
11
|
* pulling in the SDK.
|
|
12
12
|
*/
|
|
13
|
+
import modelsDevCache from "../be/modelsdev-cache.json";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* List of Codex models we know about (drives the onboarding model selector,
|
|
@@ -18,17 +19,20 @@
|
|
|
18
19
|
* SDK, so new OpenAI models work without a code change.
|
|
19
20
|
*/
|
|
20
21
|
export const CODEX_MODELS = [
|
|
21
|
-
"gpt-5.
|
|
22
|
-
"gpt-5.
|
|
22
|
+
"gpt-5.6-sol", // frontier GPT-5.6 tier for complex reasoning/coding
|
|
23
|
+
"gpt-5.6-terra", // balanced GPT-5.6 tier
|
|
24
|
+
"gpt-5.6-luna", // fast/cheap GPT-5.6 tier for high-volume workloads
|
|
25
|
+
"gpt-5.5", // previous frontier coding/professional-work model
|
|
26
|
+
"gpt-5.4", // previous mainline reasoning model w/ frontier coding
|
|
23
27
|
"gpt-5.4-mini", // faster/cheaper
|
|
24
|
-
"gpt-5.3-codex", // coding-specialized
|
|
28
|
+
"gpt-5.3-codex", // coding-specialized legacy model
|
|
25
29
|
"gpt-5.2-codex", // legacy — scheduled for retirement, see openai deprecations page
|
|
26
30
|
] as const;
|
|
27
31
|
|
|
28
32
|
export type CodexModel = (typeof CODEX_MODELS)[number];
|
|
29
33
|
|
|
30
34
|
/** The baseline default when neither MODEL_OVERRIDE nor task.model is set. */
|
|
31
|
-
export const CODEX_DEFAULT_MODEL: CodexModel = "gpt-5.
|
|
35
|
+
export const CODEX_DEFAULT_MODEL: CodexModel = "gpt-5.6-terra";
|
|
32
36
|
|
|
33
37
|
/**
|
|
34
38
|
* Map claude-style shortnames (that flow through MODEL_OVERRIDE / task.model)
|
|
@@ -36,10 +40,10 @@ export const CODEX_DEFAULT_MODEL: CodexModel = "gpt-5.4";
|
|
|
36
40
|
* a task authored for Claude works unchanged when pointed at a Codex worker.
|
|
37
41
|
*/
|
|
38
42
|
const CLAUDE_SHORTNAMES: Record<string, CodexModel> = {
|
|
39
|
-
fable: "gpt-5.
|
|
40
|
-
opus: "gpt-5.
|
|
41
|
-
sonnet: "gpt-5.
|
|
42
|
-
haiku: "gpt-5.
|
|
43
|
+
fable: "gpt-5.6-sol",
|
|
44
|
+
opus: "gpt-5.6-sol",
|
|
45
|
+
sonnet: "gpt-5.6-terra",
|
|
46
|
+
haiku: "gpt-5.6-luna",
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
/**
|
|
@@ -56,22 +60,51 @@ export function resolveCodexModel(modelStr: string | undefined): string {
|
|
|
56
60
|
return CLAUDE_SHORTNAMES[normalized] ?? normalized;
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
interface ModelsDevOpenAiModel {
|
|
64
|
+
limit?: {
|
|
65
|
+
context?: number;
|
|
66
|
+
};
|
|
67
|
+
cost?: {
|
|
68
|
+
input?: number;
|
|
69
|
+
cache_read?: number;
|
|
70
|
+
output?: number;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const MODELSDEV_OPENAI_MODELS =
|
|
75
|
+
(
|
|
76
|
+
modelsDevCache as {
|
|
77
|
+
openai?: {
|
|
78
|
+
models?: Record<string, ModelsDevOpenAiModel>;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
).openai?.models ?? {};
|
|
82
|
+
|
|
83
|
+
const FALLBACK_CODEX_MODEL_CONTEXT_WINDOWS: Record<CodexModel, number> = {
|
|
84
|
+
"gpt-5.6-sol": 1_050_000,
|
|
85
|
+
"gpt-5.6-terra": 1_050_000,
|
|
86
|
+
"gpt-5.6-luna": 1_050_000,
|
|
68
87
|
"gpt-5.5": 1_050_000,
|
|
69
|
-
"gpt-5.4":
|
|
70
|
-
"gpt-5.4-mini":
|
|
71
|
-
"gpt-5.3-codex":
|
|
88
|
+
"gpt-5.4": 1_050_000,
|
|
89
|
+
"gpt-5.4-mini": 400_000,
|
|
90
|
+
"gpt-5.3-codex": 400_000,
|
|
72
91
|
"gpt-5.2-codex": 200_000,
|
|
73
92
|
};
|
|
74
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Per-model approximate context window (tokens). The Codex SDK does not
|
|
96
|
+
* expose these at runtime, so we read the vendored models.dev cache used by
|
|
97
|
+
* the pricing seeder and fall back only for legacy/incomplete cache entries.
|
|
98
|
+
* The values are used by the `context_usage` percent calculation inside
|
|
99
|
+
* `CodexSession`.
|
|
100
|
+
*/
|
|
101
|
+
export const CODEX_MODEL_CONTEXT_WINDOWS: Record<CodexModel, number> = Object.fromEntries(
|
|
102
|
+
CODEX_MODELS.map((model) => [
|
|
103
|
+
model,
|
|
104
|
+
MODELSDEV_OPENAI_MODELS[model]?.limit?.context ?? FALLBACK_CODEX_MODEL_CONTEXT_WINDOWS[model],
|
|
105
|
+
]),
|
|
106
|
+
) as Record<CodexModel, number>;
|
|
107
|
+
|
|
75
108
|
/**
|
|
76
109
|
* Return the context window in tokens for a given Codex model. Unknown models
|
|
77
110
|
* (passthrough strings) get the 200k default — keeps `context_usage` finite
|
|
@@ -82,15 +115,14 @@ export function getCodexContextWindow(model: string): number {
|
|
|
82
115
|
}
|
|
83
116
|
|
|
84
117
|
/**
|
|
85
|
-
* Per-model pricing in USD per million tokens, sourced from
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* default to the headline rate).
|
|
118
|
+
* Per-model pricing in USD per million tokens, sourced from the vendored
|
|
119
|
+
* models.dev cache (`src/be/modelsdev-cache.json`). The fallback below mirrors
|
|
120
|
+
* that snapshot for known models and covers legacy models that models.dev no
|
|
121
|
+
* longer lists.
|
|
90
122
|
*
|
|
91
123
|
* The Codex SDK does NOT report dollar cost in `Usage`, so this map is what
|
|
92
|
-
* powers `totalCostUsd` on the `result` event.
|
|
93
|
-
*
|
|
124
|
+
* powers `totalCostUsd` on the `result` event. Refresh models.dev whenever
|
|
125
|
+
* OpenAI changes pricing or adds new models.
|
|
94
126
|
*
|
|
95
127
|
* `gpt-5.2-codex` is not on the current pricing page (legacy / retired); it
|
|
96
128
|
* inherits the `gpt-5.3-codex` rate as a best-effort fallback so old tasks
|
|
@@ -105,7 +137,22 @@ export interface CodexModelPricing {
|
|
|
105
137
|
outputPerMillion: number;
|
|
106
138
|
}
|
|
107
139
|
|
|
108
|
-
|
|
140
|
+
const FALLBACK_CODEX_MODEL_PRICING: Record<CodexModel, CodexModelPricing> = {
|
|
141
|
+
"gpt-5.6-sol": {
|
|
142
|
+
inputPerMillion: 5.0,
|
|
143
|
+
cachedInputPerMillion: 0.5,
|
|
144
|
+
outputPerMillion: 30.0,
|
|
145
|
+
},
|
|
146
|
+
"gpt-5.6-terra": {
|
|
147
|
+
inputPerMillion: 2.5,
|
|
148
|
+
cachedInputPerMillion: 0.25,
|
|
149
|
+
outputPerMillion: 15.0,
|
|
150
|
+
},
|
|
151
|
+
"gpt-5.6-luna": {
|
|
152
|
+
inputPerMillion: 1.0,
|
|
153
|
+
cachedInputPerMillion: 0.1,
|
|
154
|
+
outputPerMillion: 6.0,
|
|
155
|
+
},
|
|
109
156
|
"gpt-5.5": {
|
|
110
157
|
inputPerMillion: 5.0,
|
|
111
158
|
cachedInputPerMillion: 0.5,
|
|
@@ -134,6 +181,29 @@ export const CODEX_MODEL_PRICING: Record<CodexModel, CodexModelPricing> = {
|
|
|
134
181
|
},
|
|
135
182
|
};
|
|
136
183
|
|
|
184
|
+
function getModelsDevCodexPricing(model: CodexModel): CodexModelPricing | undefined {
|
|
185
|
+
const cost = MODELSDEV_OPENAI_MODELS[model]?.cost;
|
|
186
|
+
if (
|
|
187
|
+
typeof cost?.input !== "number" ||
|
|
188
|
+
typeof cost.cache_read !== "number" ||
|
|
189
|
+
typeof cost.output !== "number"
|
|
190
|
+
) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
inputPerMillion: cost.input,
|
|
195
|
+
cachedInputPerMillion: cost.cache_read,
|
|
196
|
+
outputPerMillion: cost.output,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export const CODEX_MODEL_PRICING: Record<CodexModel, CodexModelPricing> = Object.fromEntries(
|
|
201
|
+
CODEX_MODELS.map((model) => [
|
|
202
|
+
model,
|
|
203
|
+
getModelsDevCodexPricing(model) ?? FALLBACK_CODEX_MODEL_PRICING[model],
|
|
204
|
+
]),
|
|
205
|
+
) as Record<CodexModel, CodexModelPricing>;
|
|
206
|
+
|
|
137
207
|
/**
|
|
138
208
|
* Phase 6 — one-warning-per-process tracking so unknown models log once
|
|
139
209
|
* instead of spamming the worker log on every turn.
|