@theokit/sdk 5.14.0 → 5.15.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 +23 -0
- package/dist/{agent-Dfw3VsIX.d.ts → agent-B6C9i_qu.d.ts} +23 -213
- package/dist/{agent-DExqjqYy.d.cts → agent-B6FQad6P.d.cts} +23 -213
- package/dist/{agent-T34LCGQM.js → agent-DRDGUU6T.js} +18 -17
- package/dist/{agent-T34LCGQM.js.map → agent-DRDGUU6T.js.map} +1 -1
- package/dist/{agent-TL4K7BTG.cjs → agent-VI2HITPT.cjs} +19 -18
- package/dist/{agent-TL4K7BTG.cjs.map → agent-VI2HITPT.cjs.map} +1 -1
- package/dist/agent.d.ts +2 -3
- package/dist/{batch-EP3FCL6D.js → batch-N2XDYZED.js} +4 -4
- package/dist/{batch-EP3FCL6D.js.map → batch-N2XDYZED.js.map} +1 -1
- package/dist/{batch-IZ5EDKJ5.cjs → batch-VFR2MAUE.cjs} +7 -7
- package/dist/{batch-IZ5EDKJ5.cjs.map → batch-VFR2MAUE.cjs.map} +1 -1
- package/dist/{chunk-3ONIVO2I.js → chunk-23SS4ID4.js} +3 -3
- package/dist/{chunk-3ONIVO2I.js.map → chunk-23SS4ID4.js.map} +1 -1
- package/dist/{chunk-2C24NSNV.js → chunk-3BBTHFGJ.js} +3 -3
- package/dist/{chunk-2C24NSNV.js.map → chunk-3BBTHFGJ.js.map} +1 -1
- package/dist/{chunk-NP362THY.cjs → chunk-3F53FLQ7.cjs} +5 -7
- package/dist/chunk-3F53FLQ7.cjs.map +1 -0
- package/dist/{chunk-SXH37C7X.cjs → chunk-55LLQFHW.cjs} +6 -6
- package/dist/{chunk-SXH37C7X.cjs.map → chunk-55LLQFHW.cjs.map} +1 -1
- package/dist/{chunk-GARVAUCU.js → chunk-5M5GQYBM.js} +534 -236
- package/dist/chunk-5M5GQYBM.js.map +1 -0
- package/dist/{chunk-2WY2OFSC.cjs → chunk-64ECM3XM.cjs} +616 -318
- package/dist/chunk-64ECM3XM.cjs.map +1 -0
- package/dist/{chunk-ZJAYFSVI.js → chunk-7ACS6P5Z.js} +5 -7
- package/dist/chunk-7ACS6P5Z.js.map +1 -0
- package/dist/{chunk-NAVUTK6X.js → chunk-ARC2V7NY.js} +3 -3
- package/dist/{chunk-NAVUTK6X.js.map → chunk-ARC2V7NY.js.map} +1 -1
- package/dist/{chunk-WDVG5R2Q.cjs → chunk-AUH2EVWY.cjs} +4 -3
- package/dist/chunk-AUH2EVWY.cjs.map +1 -0
- package/dist/chunk-BNEUB52I.cjs +55 -0
- package/dist/chunk-BNEUB52I.cjs.map +1 -0
- package/dist/chunk-BPL5CVZ5.js +53 -0
- package/dist/chunk-BPL5CVZ5.js.map +1 -0
- package/dist/{chunk-NVQSBRG6.js → chunk-CBBT42VX.js} +3 -3
- package/dist/{chunk-NVQSBRG6.js.map → chunk-CBBT42VX.js.map} +1 -1
- package/dist/chunk-CHKV2L2Y.cjs +416 -0
- package/dist/chunk-CHKV2L2Y.cjs.map +1 -0
- package/dist/{chunk-SNVYOX46.cjs → chunk-DJKGDLYZ.cjs} +91 -61
- package/dist/chunk-DJKGDLYZ.cjs.map +1 -0
- package/dist/{chunk-354F3OF5.js → chunk-FGGVSHDW.js} +3 -3
- package/dist/{chunk-354F3OF5.js.map → chunk-FGGVSHDW.js.map} +1 -1
- package/dist/{chunk-6OCJWGVD.cjs → chunk-FTXY4D44.cjs} +4 -4
- package/dist/{chunk-6OCJWGVD.cjs.map → chunk-FTXY4D44.cjs.map} +1 -1
- package/dist/chunk-INVEGVGO.js +405 -0
- package/dist/chunk-INVEGVGO.js.map +1 -0
- package/dist/{chunk-ZOL3L4EH.js → chunk-IOBHFP2E.js} +49 -19
- package/dist/chunk-IOBHFP2E.js.map +1 -0
- package/dist/{chunk-BZK5QL3N.cjs → chunk-KMFBNKCT.cjs} +4 -4
- package/dist/{chunk-BZK5QL3N.cjs.map → chunk-KMFBNKCT.cjs.map} +1 -1
- package/dist/{chunk-DF77RBF4.js → chunk-LCCT5ZXJ.js} +6 -6
- package/dist/{chunk-DF77RBF4.js.map → chunk-LCCT5ZXJ.js.map} +1 -1
- package/dist/{chunk-G7XBVM3T.cjs → chunk-MHUOVS5I.cjs} +5 -5
- package/dist/{chunk-G7XBVM3T.cjs.map → chunk-MHUOVS5I.cjs.map} +1 -1
- package/dist/{chunk-CWQPQTUZ.cjs → chunk-NEH3BPFD.cjs} +15 -15
- package/dist/{chunk-CWQPQTUZ.cjs.map → chunk-NEH3BPFD.cjs.map} +1 -1
- package/dist/{chunk-5AR6O7GW.js → chunk-NK5EOTDY.js} +3 -3
- package/dist/{chunk-5AR6O7GW.js.map → chunk-NK5EOTDY.js.map} +1 -1
- package/dist/{chunk-4TEGFMSP.cjs → chunk-P7QYG4RZ.cjs} +23 -15
- package/dist/chunk-P7QYG4RZ.cjs.map +1 -0
- package/dist/chunk-PPU2D4FZ.js +19 -0
- package/dist/chunk-PPU2D4FZ.js.map +1 -0
- package/dist/{chunk-AB4OPJLD.js → chunk-RGVMJU57.js} +3 -3
- package/dist/{chunk-AB4OPJLD.js.map → chunk-RGVMJU57.js.map} +1 -1
- package/dist/{chunk-4KBI37F3.cjs → chunk-RTDVSHJR.cjs} +5 -5
- package/dist/{chunk-4KBI37F3.cjs.map → chunk-RTDVSHJR.cjs.map} +1 -1
- package/dist/{chunk-CJN5CEI3.js → chunk-RZL2W4C3.js} +23 -15
- package/dist/chunk-RZL2W4C3.js.map +1 -0
- package/dist/chunk-SPEN7RJU.cjs +18 -0
- package/dist/{chunk-R5UQL2R5.cjs.map → chunk-SPEN7RJU.cjs.map} +1 -1
- package/dist/{chunk-ZF2LDKQQ.cjs → chunk-U3DW3WDE.cjs} +9 -8
- package/dist/chunk-U3DW3WDE.cjs.map +1 -0
- package/dist/{chunk-LXG6VL6R.cjs → chunk-U7PRI3XM.cjs} +3 -3
- package/dist/{chunk-LXG6VL6R.cjs.map → chunk-U7PRI3XM.cjs.map} +1 -1
- package/dist/{chunk-H7G65PNI.js → chunk-VCQ37TC5.js} +3 -3
- package/dist/{chunk-H7G65PNI.js.map → chunk-VCQ37TC5.js.map} +1 -1
- package/dist/{chunk-SDZ4XDWS.cjs → chunk-VWYFGE27.cjs} +9 -9
- package/dist/{chunk-SDZ4XDWS.cjs.map → chunk-VWYFGE27.cjs.map} +1 -1
- package/dist/{chunk-RN3XDZGS.js → chunk-WM646HEN.js} +4 -3
- package/dist/chunk-WM646HEN.js.map +1 -0
- package/dist/chunk-X7CE7Y5W.js +305 -0
- package/dist/chunk-X7CE7Y5W.js.map +1 -0
- package/dist/{chunk-CGH4BCIY.cjs → chunk-XE3CAY6Z.cjs} +5 -5
- package/dist/{chunk-CGH4BCIY.cjs.map → chunk-XE3CAY6Z.cjs.map} +1 -1
- package/dist/chunk-XOU3CL7S.cjs +312 -0
- package/dist/chunk-XOU3CL7S.cjs.map +1 -0
- package/dist/{chunk-ULEK4OM4.js → chunk-YKNBTHMD.js} +3 -3
- package/dist/{chunk-ULEK4OM4.js.map → chunk-YKNBTHMD.js.map} +1 -1
- package/dist/{compact-session-D2MQDB6G.cjs → compact-session-NX7HUNTB.cjs} +16 -16
- package/dist/{compact-session-D2MQDB6G.cjs.map → compact-session-NX7HUNTB.cjs.map} +1 -1
- package/dist/{compact-session-N2PPHQO6.js → compact-session-Q5IGNFPW.js} +7 -7
- package/dist/{compact-session-N2PPHQO6.js.map → compact-session-Q5IGNFPW.js.map} +1 -1
- package/dist/{cron-CK2v0VWA.d.cts → cron-Bh3aWgQ2.d.cts} +2 -2
- package/dist/{cron-BubDpFJG.d.ts → cron-CCDxeeFZ.d.ts} +2 -2
- package/dist/cron.cjs +18 -17
- package/dist/cron.d.cts +4 -3
- package/dist/cron.d.ts +4 -3
- package/dist/cron.js +17 -16
- package/dist/eval.cjs +17 -16
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +16 -15
- package/dist/eval.js.map +1 -1
- package/dist/fs-session-store-CYNTEPJZ.cjs +20 -0
- package/dist/{fs-session-store-S2AXVJIX.cjs.map → fs-session-store-CYNTEPJZ.cjs.map} +1 -1
- package/dist/fs-session-store-MYQEVUFL.js +11 -0
- package/dist/{fs-session-store-6ZCCJX3K.js.map → fs-session-store-MYQEVUFL.js.map} +1 -1
- package/dist/{index-manager-HO456AVW.cjs → index-manager-2KH75BRW.cjs} +7 -7
- package/dist/{index-manager-HO456AVW.cjs.map → index-manager-2KH75BRW.cjs.map} +1 -1
- package/dist/{index-manager-QDH56FER.js → index-manager-NF2WRLOU.js} +6 -6
- package/dist/{index-manager-QDH56FER.js.map → index-manager-NF2WRLOU.js.map} +1 -1
- package/dist/index.cjs +66 -262
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -46
- package/dist/index.d.ts +10 -46
- package/dist/index.js +27 -227
- package/dist/index.js.map +1 -1
- package/dist/{inject-session-J26SHHIO.js → inject-session-4ROYLZHY.js} +3 -3
- package/dist/{inject-session-J26SHHIO.js.map → inject-session-4ROYLZHY.js.map} +1 -1
- package/dist/{inject-session-3ICZIJ2N.cjs → inject-session-AWKDCGHV.cjs} +4 -4
- package/dist/{inject-session-3ICZIJ2N.cjs.map → inject-session-AWKDCGHV.cjs.map} +1 -1
- package/dist/internal/agent-loop/doom-loop-tracker.d.ts +3 -0
- package/dist/internal/agent-loop/types.d.ts +6 -0
- package/dist/internal/error-mappers/anthropic.d.ts +3 -0
- package/dist/internal/error-mappers/openai-compatible.d.ts +2 -3
- package/dist/internal/local-agent/local-agent-dispatch.d.ts +3 -1
- package/dist/internal/local-agent/send-queue.d.ts +31 -0
- package/dist/internal/mcp/client.d.ts +2 -0
- package/dist/internal/mcp/oauth-bearer.d.ts +2 -0
- package/dist/internal/memory/adapters/index.cjs +3 -3
- package/dist/internal/memory/adapters/index.js +2 -2
- package/dist/internal/memory/dreaming/phases.d.ts +15 -0
- package/dist/internal/memory/dreaming/run.d.ts +29 -0
- package/dist/internal/memory/sdk-memory-peer-loader.d.ts +2 -0
- package/dist/internal/memory/storage/index.cjs +51 -35
- package/dist/internal/memory/storage/index.d.cts +2 -0
- package/dist/internal/memory/storage/index.d.ts +2 -0
- package/dist/internal/memory/storage/index.js +4 -4
- package/dist/internal/persistence/cwd-mutex.d.cts +4 -2
- package/dist/internal/persistence/cwd-mutex.d.ts +4 -2
- package/dist/internal/persistence/index.cjs +4 -4
- package/dist/internal/persistence/index.js +2 -2
- package/dist/internal/persistence/sqlite-session-store.d.cts +25 -0
- package/dist/internal/persistence/sqlite-session-store.d.ts +25 -0
- package/dist/internal/providers/registry.d.ts +5 -1
- package/dist/mcp-auth.cjs +25 -307
- package/dist/mcp-auth.cjs.map +1 -1
- package/dist/mcp-auth.js +5 -305
- package/dist/mcp-auth.js.map +1 -1
- package/dist/models.cjs +9 -9
- package/dist/models.js +2 -2
- package/dist/persistence.cjs +9 -4
- package/dist/persistence.cjs.map +1 -1
- package/dist/persistence.d.cts +744 -40
- package/dist/persistence.d.ts +744 -40
- package/dist/persistence.js +3 -2
- package/dist/persistence.js.map +1 -1
- package/dist/providers.cjs +4 -4
- package/dist/providers.js +2 -2
- package/dist/sandbox/docker-sandbox.d.cts +58 -0
- package/dist/sandbox/docker-sandbox.d.ts +58 -0
- package/dist/sandbox/index.cjs +129 -1
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.d.cts +1 -0
- package/dist/sandbox/index.d.ts +1 -0
- package/dist/sandbox/index.js +132 -5
- package/dist/sandbox/index.js.map +1 -1
- package/dist/{sdk-agent-DwzHcuxy.d.cts → sdk-agent-BA_6_wtc.d.cts} +30 -2
- package/dist/{sdk-agent-a3fW1xUK.d.ts → sdk-agent-CnwPODYH.d.ts} +30 -2
- package/dist/session-store-Dh07jr9N.d.cts +209 -0
- package/dist/session-store-Dh07jr9N.d.ts +209 -0
- package/dist/sqlite-session-store-BbcpKY1L.d.ts +64 -0
- package/dist/sqlite-session-store-TKXtT4ON.d.cts +64 -0
- package/dist/subagents-loader-KLWF4QPD.cjs +16 -0
- package/dist/{subagents-loader-TXOA5EJJ.cjs.map → subagents-loader-KLWF4QPD.cjs.map} +1 -1
- package/dist/subagents-loader-SXETS2F2.js +7 -0
- package/dist/{subagents-loader-Z744TAKD.js.map → subagents-loader-SXETS2F2.js.map} +1 -1
- package/dist/subagents-loader.cjs +2 -2
- package/dist/subagents-loader.d.cts +3 -2
- package/dist/subagents-loader.d.ts +3 -2
- package/dist/subagents-loader.js +1 -1
- package/dist/types/agent.d.ts +14 -1
- package/dist/types/budget-tracker.d.ts +6 -2
- package/dist/types/mcp.d.ts +14 -0
- package/dist/types/run.d.ts +15 -1
- package/dist/workflow.d.cts +1 -1
- package/dist/workflow.d.ts +1 -1
- package/docs/adr/mutation-testing-scope.md +12 -2
- package/docs/error-codes.md +41 -40
- package/docs/harness-capability-map.md +32 -6
- package/package.json +1 -1
- package/dist/chunk-2WY2OFSC.cjs.map +0 -1
- package/dist/chunk-4TEGFMSP.cjs.map +0 -1
- package/dist/chunk-6CXRYPG7.cjs +0 -211
- package/dist/chunk-6CXRYPG7.cjs.map +0 -1
- package/dist/chunk-CJN5CEI3.js.map +0 -1
- package/dist/chunk-GARVAUCU.js.map +0 -1
- package/dist/chunk-MSEL5E5P.js +0 -204
- package/dist/chunk-MSEL5E5P.js.map +0 -1
- package/dist/chunk-NP362THY.cjs.map +0 -1
- package/dist/chunk-Q5EWJPRY.js +0 -18
- package/dist/chunk-Q5EWJPRY.js.map +0 -1
- package/dist/chunk-R5UQL2R5.cjs +0 -18
- package/dist/chunk-RN3XDZGS.js.map +0 -1
- package/dist/chunk-SNVYOX46.cjs.map +0 -1
- package/dist/chunk-WDVG5R2Q.cjs.map +0 -1
- package/dist/chunk-ZF2LDKQQ.cjs.map +0 -1
- package/dist/chunk-ZJAYFSVI.js.map +0 -1
- package/dist/chunk-ZOL3L4EH.js.map +0 -1
- package/dist/fs-session-store-6ZCCJX3K.js +0 -11
- package/dist/fs-session-store-S2AXVJIX.cjs +0 -20
- package/dist/subagents-loader-TXOA5EJJ.cjs +0 -16
- package/dist/subagents-loader-Z744TAKD.js +0 -7
|
@@ -152,7 +152,8 @@ function rejectMcp(fields, filename) {
|
|
|
152
152
|
}
|
|
153
153
|
function resolveModel(fields, filename) {
|
|
154
154
|
const modelId = asString(fields.model);
|
|
155
|
-
const
|
|
155
|
+
const rawEffort = asString(fields.reasoning_effort)?.trim();
|
|
156
|
+
const effort = rawEffort === "" ? void 0 : rawEffort;
|
|
156
157
|
if (effort !== void 0 && (modelId === void 0 || modelId === "inherit")) {
|
|
157
158
|
throw new ConfigurationError(
|
|
158
159
|
`Subagent ${filename}: reasoning_effort requires a concrete model (effort is a model parameter; an absent model or "inherit" cannot carry it)`,
|
|
@@ -202,5 +203,5 @@ function parseFrontmatterFields(frontmatter) {
|
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
export { loadSubagents, pluginBundleDirs };
|
|
205
|
-
//# sourceMappingURL=chunk-
|
|
206
|
-
//# sourceMappingURL=chunk-
|
|
206
|
+
//# sourceMappingURL=chunk-WM646HEN.js.map
|
|
207
|
+
//# sourceMappingURL=chunk-WM646HEN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/runtime/plugin-loader/plugin-bundles.ts","../src/internal/runtime/skills/subagents-loader.ts"],"names":["join"],"mappings":"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","file":"chunk-WM646HEN.js","sourcesContent":["/**\n * Locating the plugin bundles a project carries.\n *\n * Shared by the skills and subagents loaders, which both need the same answer to \"which folders in\n * this project are plugins\" and would otherwise each grow their own copy of the directory walk.\n *\n * @internal\n */\n\nimport type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { pluginBundleRoots } from \"../../persistence/paths.js\";\nimport type { CompatSourceDeclaration } from \"../compat/foreign-config-sources.js\";\n\n/**\n * Every plugin folder under the project's plugin roots.\n *\n * Returns the FOLDERS, not their contents — what a bundle contributes (`skills/`, `agents/`) is the\n * caller's business, and a loader that also knew the layout would have to change whenever the other\n * one did.\n *\n * A missing root is not an error: most projects carry no plugins, and treating their absence as a\n * failure would make \"none installed\" indistinguishable from \"the directory could not be read\".\n */\nexport async function pluginBundleDirs(\n cwd: string,\n /** Declared foreign dialects (#524). Empty reads `.theokit/plugins` only. */\n compatSources: readonly CompatSourceDeclaration[] = [],\n): Promise<string[]> {\n const dirs: string[] = [];\n for (const root of pluginBundleRoots(cwd, compatSources)) {\n let entries: Dirent[];\n try {\n entries = await readdir(root, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) dirs.push(join(root, entry.name));\n }\n }\n return dirs;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport { ConfigurationError } from \"../../../errors.js\";\nimport type { AgentDefinition } from \"../../../types/agent.js\";\nimport type { ModelSelection } from \"../../../types/agent-prims.js\";\nimport { diag } from \"../../diagnostics.js\";\nimport { projectConfigRoots } from \"../../persistence/paths.js\";\nimport type { CompatSourceDeclaration } from \"../compat/foreign-config-sources.js\";\nimport { readWorkspaceDir } from \"../config/workspace-dir.js\";\nimport { type FrontmatterValue, parseSimpleYaml } from \"../context/yaml-frontmatter.js\";\nimport { pluginBundleDirs } from \"../plugin-loader/plugin-bundles.js\";\n\n/**\n * Load file-based subagents from `.theokit/agents/*.md` and merge with\n * inline definitions. Inline overrides file-based on name conflict.\n *\n * Each markdown file has YAML frontmatter (description + optional model)\n * and a body that becomes the subagent prompt.\n *\n * @internal\n */\nexport async function loadSubagents(\n cwd: string,\n settingSourcesIncludeProject: boolean,\n inline: Record<string, AgentDefinition> | undefined,\n /** Declared foreign dialects (#524). Empty reads `.theokit/` only. */\n compatSources: readonly CompatSourceDeclaration[] = [],\n): Promise<Record<string, AgentDefinition>> {\n const result: Record<string, AgentDefinition> = {};\n if (settingSourcesIncludeProject) {\n const projectAgents = await readProjectSubagents(cwd, compatSources);\n for (const [name, definition] of Object.entries(projectAgents)) {\n result[name] = definition;\n }\n }\n if (inline !== undefined) {\n for (const [name, definition] of Object.entries(inline)) {\n result[name] = definition;\n }\n }\n return result;\n}\n\n/**\n * Read agent declarations from every project config root (`.theokit`, then `.claude`).\n *\n * FIRST occurrence of a name wins, which is what makes `projectConfigRoots`' order a contract rather\n * than a detail: a project declaring the same agent in both means the explicit namespace.\n */\nasync function readProjectSubagents(\n cwd: string,\n compatSources: readonly CompatSourceDeclaration[],\n): Promise<Record<string, AgentDefinition>> {\n const subagents: Record<string, AgentDefinition> = {};\n for (const configRoot of projectConfigRoots(cwd, compatSources, \"subagents\")) {\n await readSubagentsFrom(join(configRoot, \"agents\"), subagents);\n }\n // A Claude Code plugin is a BUNDLE, and its `agents/` is what it exists to contribute. Read after\n // the project's own, so a project can shadow an agent a plugin ships without editing the plugin.\n for (const bundle of await pluginBundleDirs(cwd, compatSources)) {\n await readSubagentsFrom(join(bundle, \"agents\"), subagents);\n }\n return subagents;\n}\n\n/**\n * Parse an agent file, or return null when it was written for another runtime.\n *\n * ## What this changed, and what it deliberately did not\n *\n * The failure used to take the whole directory, and that was a decision rather than an oversight:\n * \"a file that HAS frontmatter and gets it wrong is a broken agent and still fails loudly, which is\n * what keeps a typo'd `sandbox` from returning as a silent gate through this door. Isolating the\n * failure per file would hand that risk back.\"\n *\n * The risk named there is real and is NOT handed back, because the isolation is narrower than the\n * one that argument refuses. Only `KNOWN_CLAUDE_CODE_FIELDS` takes this door — the set that already\n * encodes \"this key belongs to another runtime and carries no behaviour here\". A misspelling of one\n * of OUR keys carries `subagent_unknown_field` and still propagates, so `sandboxx` remains fatal\n * and cannot return as a silent gate. `a-claude-code-field-names-itself.test.ts` pins both halves.\n *\n * What moved the decision was the cost, measured in a live TUI on 2026-09-15: one ported\n * `.claude/agents/*.md` carrying `memory:` stopped every sibling agent from loading and the turn\n * produced no answer at all — on an agent the task never used. Beside it, on the same prompt and\n * the same project, another runtime answered normally. The diagnosis fix this file shipped tells a\n * user which key is foreign; it does not give them back the turn.\n *\n * The loader already draws exactly this line for a file with no frontmatter, and for the same\n * stated reason: one of them must not stop every agent in the directory from loading.\n */\nfunction parseOrSkipForeignRuntime(\n raw: string,\n filename: string,\n): { name: string; definition: AgentDefinition } | null {\n try {\n return parseSubagentMarkdown(raw, filename);\n } catch (error) {\n if (error instanceof ConfigurationError && error.code === \"subagent_foreign_runtime_field\") {\n diag(`[theokit-sdk] ${filename}: ${error.message} — skipping this agent`);\n return null;\n }\n throw error;\n }\n}\n\nasync function readSubagentsFrom(\n root: string,\n subagents: Record<string, AgentDefinition>,\n): Promise<void> {\n const entries = await readWorkspaceDir(root, \"subagents_read_error\", \"subagents directory\");\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue;\n const path = join(root, entry.name);\n const raw = await readFile(path, \"utf8\");\n // A markdown file with NO frontmatter is not an agent declaration — a directory of agents\n // written for the Claude Code CLI conventionally carries documentation beside them, and\n // `.claude/agents/README.md` exists in this repository. Throwing on it made ONE such file stop\n // every agent in the directory from loading.\n //\n // Skipped with a warn rather than in silence, and ONLY for the no-frontmatter case: a file that\n // HAS frontmatter and gets it wrong is a broken agent and still fails loudly, which is what\n // keeps a typo'd `sandbox` from returning as a silent gate through this door.\n if (!hasFrontmatter(raw)) {\n diag(`[theokit-sdk] ${entry.name} has no frontmatter — not an agent declaration, skipping`);\n continue;\n }\n const definition = parseOrSkipForeignRuntime(raw, entry.name);\n if (definition === null) continue;\n if (subagents[definition.name] === undefined) {\n // `path` is computed above to read the file and was then dropped. Keeping it is the whole\n // visibility fix (#524): without it a listing cannot say which root an agent came from.\n subagents[definition.name] = { ...definition.definition, source: path };\n }\n }\n}\n\n// The frontmatter keys a disk subagent may declare. Any other key is a typed load\n// error rather than a silent drop — a dropped `sandbox` an operator wrote believing\n// it confines the child is exactly the silent-gate failure class this guards against.\nconst ACCEPTED_FIELDS = new Set([\n \"name\",\n \"description\",\n \"model\",\n \"tools\",\n \"reasoning_effort\",\n \"mcp\",\n \"sandbox\",\n \"memory\",\n]);\n\n// Fields the Claude Code CLI writes that carry NO behaviour for this runtime. Accepted and ignored,\n// so an agent authored for the CLI loads here unchanged — measured 2026-08-26 across the 59 agent\n// files on one machine, where `color` appeared in 38 of them and made every one of those a\n// `subagent_unknown_field` load error.\n//\n// Named explicitly instead of loosening the check above, because that check's reason is sound: a\n// dropped `sandbox` an operator wrote believing it confines the child is a silent gate. A field that\n// COULD change behaviour must still fail loudly. This set is the difference between \"we know this\n// one and it does nothing\" and \"we have never heard of this\" — two facts a bare allow-everything\n// would collapse into one.\n//\n// Anything added here needs the same justification: inert for THIS runtime, not merely unfamiliar.\nconst INERT_CLAUDE_CODE_FIELDS = new Set([\n /** The CLI's label colour for the agent. Presentation only. */\n \"color\",\n]);\n\nfunction parseSubagentMarkdown(\n raw: string,\n filename: string,\n): { name: string; definition: AgentDefinition } {\n const { frontmatter, body } = splitFrontmatter(raw, filename);\n const fields = parseFrontmatterFields(frontmatter);\n rejectUnknownFields(fields, filename);\n rejectMcp(fields, filename);\n\n const definition: AgentDefinition = {\n description: asString(fields.description) ?? \"\",\n prompt: body,\n };\n const model = resolveModel(fields, filename);\n if (model !== undefined) definition.model = model;\n const tools = toStringList(fields.tools);\n if (tools.length > 0) definition.tools = tools;\n const sandbox = resolveSandbox(fields, filename);\n if (sandbox !== undefined) definition.sandbox = sandbox;\n // Carried unjudged — see `AgentDefinition.memory`. Which root a scope names, and whether the\n // name is one at all, is `@theokit/agents`' decision and refusing it twice means two lists.\n const memory = asString(fields.memory);\n if (memory !== undefined) definition.memory = memory;\n\n const name = asString(fields.name) ?? filename.replace(/\\.md$/, \"\");\n return { name, definition };\n}\n\n/**\n * Frontmatter keys that Claude Code documents for a subagent and this runtime does not accept.\n *\n * Not an allow-list — every one of these still fails the load. It exists so the FAILURE can say\n * which kind of unknown it met. A user migrating a `.claude/agents/` tree used to learn one key per\n * round trip: fix `memory`, meet `permissionMode`, fix that, meet `maxTurns` — with nothing saying\n * the set was finite or that the tree was simply written for another runtime.\n *\n * The distinction this file already draws for `INERT_CLAUDE_CODE_FIELDS` is the same one: \"the\n * difference between 'we know this one and it does nothing' and 'we have never heard of this' — two\n * facts a bare allow-everything would collapse into one.\" This is the third fact beside them —\n * \"another runtime's field\" — and it changes only the message, never the verdict.\n */\nconst KNOWN_CLAUDE_CODE_FIELDS = new Set([\n \"disallowedTools\",\n \"permissionMode\",\n \"maxTurns\",\n \"skills\",\n \"background\",\n \"effort\",\n \"isolation\",\n \"initialPrompt\",\n \"hooks\",\n \"experimental\",\n \"mcpServers\",\n]);\n\n/**\n * The typed error for a key we do not accept. Built here rather than inline so `rejectUnknownFields`\n * stays a plain loop: the message has two shapes and the code has two values, and computing all four\n * at the throw site is what pushed that function past the cognitive-complexity gate.\n */\nfunction unknownFieldError(key: string, filename: string): ConfigurationError {\n const foreign = KNOWN_CLAUDE_CODE_FIELDS.has(key);\n const siblings = [...KNOWN_CLAUDE_CODE_FIELDS].filter((f) => f !== key);\n const origin = foreign\n ? ` — \"${key}\" is a Claude Code subagent field that this runtime does not support. ` +\n `The same applies to: ${siblings.join(\", \")}. Removing them one at a time will meet each ` +\n `in turn; the tree is written for another runtime`\n : \"\";\n // The two codes are written as LITERALS, one per branch, rather than as a ternary inside the\n // options object. `tools/generate-error-codes.mjs` walks the source for the literal assigned to\n // `code:`, so a ternary there is not a literal and it extracted NEITHER — measured 2026-09-15,\n // when `quality:docs-errors` went red and the regenerated reference had lost\n // `subagent_unknown_field` without ever gaining `subagent_foreign_runtime_field`. Two real,\n // throwable codes were absent from the published error reference, which is the one place a\n // consumer looks to find out what they can catch.\n if (foreign)\n return new ConfigurationError(\n `Subagent ${filename}: unknown frontmatter field \"${key}\" (accepted: ${[...ACCEPTED_FIELDS].join(\", \")})${origin}`,\n { code: \"subagent_foreign_runtime_field\" },\n );\n return new ConfigurationError(\n `Subagent ${filename}: unknown frontmatter field \"${key}\" (accepted: ${[...ACCEPTED_FIELDS].join(\", \")})${origin}`,\n { code: \"subagent_unknown_field\" },\n );\n}\n\nfunction rejectUnknownFields(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): void {\n for (const key of Object.keys(fields)) {\n if (INERT_CLAUDE_CODE_FIELDS.has(key)) continue;\n if (!ACCEPTED_FIELDS.has(key)) throw unknownFieldError(key, filename);\n }\n}\n\n// mcp: a known field, but not yet honored on the LOCAL delegation path. The frontmatter YAML can only\n// express server NAMES (parseSimpleYaml has no nested-object support), while a child's `Agent.create`\n// needs `mcpServers` as a Record<name, config>; resolving names→config per-subagent in local delegation\n// is its own follow-up. Rather than silently drop it (the M26/M32 silent-gate class), it is a typed load\n// error that names the field and points at the alternative.\nfunction rejectMcp(fields: Record<string, FrontmatterValue | undefined>, filename: string): void {\n if (fields.mcp !== undefined) {\n throw new ConfigurationError(\n `Subagent ${filename}: per-subagent \"mcp\" is not yet supported on the local delegation path; declare MCP servers in .theokit/mcp.json (or the parent) instead`,\n { code: \"subagent_mcp_unsupported_local\" },\n );\n }\n}\n\n// model + reasoning_effort — effort rides inside `model.params[thinking]`, so it requires a concrete\n// model id to attach to (a child inheriting the parent's model cannot carry the parent's provider-\n// specific effort param safely).\nfunction resolveModel(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): ModelSelection | \"inherit\" | undefined {\n const modelId = asString(fields.model);\n // An empty/blank effort (`reasoning_effort: \"\"`) names nothing: treated as absent, never as a\n // `{ id: \"thinking\", value: \"\" }` param the provider would drop or reject.\n const rawEffort = asString(fields.reasoning_effort)?.trim();\n const effort = rawEffort === \"\" ? undefined : rawEffort;\n // reasoning_effort rides in model.params[thinking], so it needs a CONCRETE model id to attach to.\n // Neither an absent model NOR `model: inherit` can carry it (the inherited id is unknown at load), so\n // both are typed errors rather than a silently-dropped effort — the silent-gate class this guards.\n if (effort !== undefined && (modelId === undefined || modelId === \"inherit\")) {\n throw new ConfigurationError(\n `Subagent ${filename}: reasoning_effort requires a concrete model (effort is a model parameter; an absent model or \"inherit\" cannot carry it)`,\n { code: \"subagent_reasoning_effort_without_model\" },\n );\n }\n if (modelId === undefined) return undefined;\n if (modelId === \"inherit\") return \"inherit\";\n return effort !== undefined\n ? { id: modelId, params: [{ id: \"thinking\", value: effort }] }\n : { id: modelId };\n}\n\n// sandbox: boolean only. A granular mode string (read-only/…) is unsupported by the SDK runtime and is\n// a typed error rather than a silent coercion to a boolean.\nfunction resolveSandbox(\n fields: Record<string, FrontmatterValue | undefined>,\n filename: string,\n): boolean | undefined {\n if (fields.sandbox === undefined) return undefined;\n if (typeof fields.sandbox !== \"boolean\") {\n throw new ConfigurationError(\n `Subagent ${filename}: sandbox must be a boolean (got \"${String(fields.sandbox)}\"); granular sandbox modes are not supported by the runtime`,\n { code: \"subagent_sandbox_not_boolean\" },\n );\n }\n return fields.sandbox;\n}\n\nfunction asString(v: FrontmatterValue | undefined): string | undefined {\n if (typeof v !== \"string\") return undefined;\n // parseSimpleYaml does not strip quotes (documented), and `model`/`reasoning_effort` are fields users\n // habitually quote (`model: \"openai/gpt-4o\"`). Strip a single matching surrounding quote pair so a\n // quoted id/effort does not slip past validation and fail only at the provider.\n const m = /^([\"'])(.*)\\1$/.exec(v);\n return m ? m[2] : v;\n}\n\n/** Accept a YAML list (`string[]`) or a comma/space-separated scalar; trim + drop empties. */\nfunction toStringList(v: FrontmatterValue | undefined): string[] {\n if (Array.isArray(v)) return v.map((t) => t.trim()).filter((t) => t.length > 0);\n if (typeof v === \"string\") {\n return v\n .split(/[\\s,]+/)\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n }\n return [];\n}\n\n/** Does this file open with a frontmatter block at all? Its ABSENCE means \"not an agent\". */\nfunction hasFrontmatter(raw: string): boolean {\n return /^---\\s*\\n/.test(raw);\n}\n\nfunction splitFrontmatter(raw: string, filename: string): { frontmatter: string; body: string } {\n const match = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n([\\s\\S]*)$/.exec(raw);\n if (match === null) {\n throw new ConfigurationError(`Subagent ${filename} is missing frontmatter`, {\n code: \"subagent_missing_frontmatter\",\n });\n }\n return { frontmatter: match[1] ?? \"\", body: (match[2] ?? \"\").trim() };\n}\n\nfunction parseFrontmatterFields(frontmatter: string): Record<string, FrontmatterValue | undefined> {\n // Preserve the rich YAML value types (boolean/number/string[]): `sandbox: true` and\n // `mcp: [a, b]` are meaningful here, so narrowing everything to string (as the\n // pre-M33 loader did) would drop them. Per-field validation happens in parseSubagentMarkdown.\n return parseSimpleYaml(frontmatter);\n}\n"]}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { atomicWriteJson } from './chunk-M4U24U6G.js';
|
|
2
|
+
import { assertSecureModes } from './chunk-QD6B6W6I.js';
|
|
3
|
+
import { ConfigurationError } from './chunk-TNFOU6SB.js';
|
|
4
|
+
import { diag } from './chunk-3OY6H3I7.js';
|
|
5
|
+
import { existsSync, readFileSync, chmodSync, mkdirSync } from 'fs';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
import { homedir } from 'os';
|
|
8
|
+
import { dirname, join } from 'path';
|
|
9
|
+
import { randomBytes, createHash } from 'crypto';
|
|
10
|
+
import { createServer } from 'http';
|
|
11
|
+
import { createInterface } from 'readline/promises';
|
|
12
|
+
|
|
13
|
+
var KEYTAR_SERVICE = "theokit-mcp";
|
|
14
|
+
function storeFilePath() {
|
|
15
|
+
return storeLocation().file;
|
|
16
|
+
}
|
|
17
|
+
function storeLocation() {
|
|
18
|
+
const override = process.env.THEOKIT_HOME?.trim();
|
|
19
|
+
if (override !== void 0 && override.length > 0) {
|
|
20
|
+
return { dir: override, file: join(override, "mcp-tokens.json"), ours: false };
|
|
21
|
+
}
|
|
22
|
+
const fromEnv = process.platform === "win32" ? process.env.USERPROFILE?.trim() : process.env.HOME?.trim();
|
|
23
|
+
const home = fromEnv !== void 0 && fromEnv.length > 0 ? fromEnv : homedir();
|
|
24
|
+
const dir = join(home, ".theokit");
|
|
25
|
+
return { dir, file: join(dir, "mcp-tokens.json"), ours: true };
|
|
26
|
+
}
|
|
27
|
+
function ensurePrivateStoreDir({ dir, ours }) {
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
30
|
+
if (ours) chmodSync(dir, 448);
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
var keytarWarned = false;
|
|
35
|
+
var cachedKeytar;
|
|
36
|
+
function tryRequireKeytar() {
|
|
37
|
+
if (cachedKeytar !== void 0) return cachedKeytar;
|
|
38
|
+
try {
|
|
39
|
+
const r = createRequire(import.meta.url);
|
|
40
|
+
cachedKeytar = r("keytar");
|
|
41
|
+
} catch {
|
|
42
|
+
if (!keytarWarned) {
|
|
43
|
+
diag(
|
|
44
|
+
`[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ${storeFilePath()} (chmod 600 where POSIX). Install \`keytar\` for OS keychain storage.
|
|
45
|
+
`
|
|
46
|
+
);
|
|
47
|
+
keytarWarned = true;
|
|
48
|
+
}
|
|
49
|
+
cachedKeytar = null;
|
|
50
|
+
}
|
|
51
|
+
return cachedKeytar;
|
|
52
|
+
}
|
|
53
|
+
var inflightRefresh = /* @__PURE__ */ new Map();
|
|
54
|
+
async function setTokens(serverName, tokens) {
|
|
55
|
+
const kt = tryRequireKeytar();
|
|
56
|
+
const payload = JSON.stringify(tokens);
|
|
57
|
+
if (kt !== null && kt !== void 0) {
|
|
58
|
+
await kt.setPassword(KEYTAR_SERVICE, serverName, payload);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const location = storeLocation();
|
|
62
|
+
const filePath = location.file;
|
|
63
|
+
ensurePrivateStoreDir(location);
|
|
64
|
+
let allTokens = {};
|
|
65
|
+
if (existsSync(filePath)) {
|
|
66
|
+
try {
|
|
67
|
+
allTokens = JSON.parse(readFileSync(filePath, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
allTokens = {};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
allTokens[serverName] = tokens;
|
|
73
|
+
await atomicWriteJson(filePath, allTokens);
|
|
74
|
+
try {
|
|
75
|
+
chmodSync(filePath, 384);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function getTokens(serverName) {
|
|
80
|
+
const kt = tryRequireKeytar();
|
|
81
|
+
if (kt !== null && kt !== void 0) {
|
|
82
|
+
const value = await kt.getPassword(KEYTAR_SERVICE, serverName);
|
|
83
|
+
if (typeof value !== "string") return void 0;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(value);
|
|
86
|
+
} catch {
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const filePath = storeFilePath();
|
|
91
|
+
if (!existsSync(filePath)) return void 0;
|
|
92
|
+
assertSecureModes(dirname(filePath), filePath);
|
|
93
|
+
try {
|
|
94
|
+
const all = JSON.parse(readFileSync(filePath, "utf8"));
|
|
95
|
+
return all[serverName];
|
|
96
|
+
} catch {
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function lockedRefresh(serverName, refreshFn) {
|
|
101
|
+
const existing = inflightRefresh.get(serverName);
|
|
102
|
+
if (existing !== void 0) return existing;
|
|
103
|
+
const promise = (async () => {
|
|
104
|
+
try {
|
|
105
|
+
return await refreshFn();
|
|
106
|
+
} finally {
|
|
107
|
+
inflightRefresh.delete(serverName);
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
inflightRefresh.set(serverName, promise);
|
|
111
|
+
return promise;
|
|
112
|
+
}
|
|
113
|
+
function base64url(n) {
|
|
114
|
+
return randomBytes(n).toString("base64url");
|
|
115
|
+
}
|
|
116
|
+
function challengeFor(verifier) {
|
|
117
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
118
|
+
}
|
|
119
|
+
async function refreshAccessToken(serverName, clientId, refreshToken, oauth) {
|
|
120
|
+
const body = new URLSearchParams({
|
|
121
|
+
grant_type: "refresh_token",
|
|
122
|
+
refresh_token: refreshToken,
|
|
123
|
+
client_id: clientId
|
|
124
|
+
});
|
|
125
|
+
const response = await fetch(oauth.tokenEndpoint, {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
128
|
+
body: body.toString()
|
|
129
|
+
});
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {
|
|
132
|
+
code: "oauth_refresh_failed"
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const data = await response.json();
|
|
136
|
+
const obtainedAt = Date.now();
|
|
137
|
+
const tokens = {
|
|
138
|
+
accessToken: data.access_token,
|
|
139
|
+
refreshToken: data.refresh_token ?? refreshToken,
|
|
140
|
+
// EC-10: default conservative 3600s if expires_in missing.
|
|
141
|
+
expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1e3,
|
|
142
|
+
obtainedAt,
|
|
143
|
+
...data.scope !== void 0 ? { scope: data.scope } : {}
|
|
144
|
+
};
|
|
145
|
+
await setTokens(serverName, tokens);
|
|
146
|
+
return tokens;
|
|
147
|
+
}
|
|
148
|
+
async function runPkceFlow(serverName, clientId, scopes, oauth) {
|
|
149
|
+
const verifier = base64url(48);
|
|
150
|
+
const challenge = challengeFor(verifier);
|
|
151
|
+
const state = base64url(16);
|
|
152
|
+
const timeoutMs = oauth.timeoutMs ?? 3e5;
|
|
153
|
+
let redirectUri;
|
|
154
|
+
let codeWaiter;
|
|
155
|
+
let cleanup = () => {
|
|
156
|
+
};
|
|
157
|
+
if (oauth.redirectMode === "localhost") {
|
|
158
|
+
const result = await openLocalhostCallback(state, timeoutMs);
|
|
159
|
+
redirectUri = result.redirectUri;
|
|
160
|
+
codeWaiter = result.waitFor;
|
|
161
|
+
cleanup = result.close;
|
|
162
|
+
} else {
|
|
163
|
+
redirectUri = "urn:ietf:wg:oauth:2.0:oob";
|
|
164
|
+
codeWaiter = readCodeFromStdin(state, timeoutMs);
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const authUrl = new URL(oauth.authorizationEndpoint);
|
|
168
|
+
authUrl.searchParams.set("response_type", "code");
|
|
169
|
+
authUrl.searchParams.set("client_id", clientId);
|
|
170
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
171
|
+
if (scopes !== void 0 && scopes.length > 0) {
|
|
172
|
+
authUrl.searchParams.set("scope", scopes.join(" "));
|
|
173
|
+
}
|
|
174
|
+
authUrl.searchParams.set("code_challenge", challenge);
|
|
175
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
176
|
+
authUrl.searchParams.set("state", state);
|
|
177
|
+
diag(
|
|
178
|
+
`
|
|
179
|
+
[theokit-sdk] OAuth required for MCP server "${serverName}".
|
|
180
|
+
Open this URL to authenticate:
|
|
181
|
+
${authUrl.toString()}
|
|
182
|
+
|
|
183
|
+
`
|
|
184
|
+
);
|
|
185
|
+
const { code, state: returnedState } = await codeWaiter;
|
|
186
|
+
if (returnedState !== state) {
|
|
187
|
+
throw new ConfigurationError(
|
|
188
|
+
"OAuth state mismatch \u2014 possible CSRF attempt; aborting token exchange.",
|
|
189
|
+
{ code: "oauth_state_mismatch" }
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
const tokenBody = new URLSearchParams({
|
|
193
|
+
grant_type: "authorization_code",
|
|
194
|
+
code,
|
|
195
|
+
client_id: clientId,
|
|
196
|
+
code_verifier: verifier,
|
|
197
|
+
redirect_uri: redirectUri
|
|
198
|
+
});
|
|
199
|
+
const response = await fetch(oauth.tokenEndpoint, {
|
|
200
|
+
method: "POST",
|
|
201
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
202
|
+
body: tokenBody.toString()
|
|
203
|
+
});
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
const errText = await response.text();
|
|
206
|
+
throw new ConfigurationError(
|
|
207
|
+
`OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,
|
|
208
|
+
{ code: "oauth_token_exchange_failed" }
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const data = await response.json();
|
|
212
|
+
const obtainedAt = Date.now();
|
|
213
|
+
return {
|
|
214
|
+
accessToken: data.access_token,
|
|
215
|
+
...data.refresh_token !== void 0 ? { refreshToken: data.refresh_token } : {},
|
|
216
|
+
// EC-10: default 3600s when expires_in absent.
|
|
217
|
+
expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1e3,
|
|
218
|
+
obtainedAt,
|
|
219
|
+
...data.scope !== void 0 ? { scope: data.scope } : {}
|
|
220
|
+
};
|
|
221
|
+
} finally {
|
|
222
|
+
cleanup();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function openLocalhostCallback(expectedState, timeoutMs) {
|
|
226
|
+
let resolveCode;
|
|
227
|
+
let rejectCode;
|
|
228
|
+
const waitFor = new Promise((res, rej) => {
|
|
229
|
+
resolveCode = res;
|
|
230
|
+
rejectCode = rej;
|
|
231
|
+
});
|
|
232
|
+
const server = createServer((req, res) => {
|
|
233
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
234
|
+
const code = url.searchParams.get("code");
|
|
235
|
+
const state = url.searchParams.get("state");
|
|
236
|
+
if (typeof code !== "string" || typeof state !== "string") {
|
|
237
|
+
res.statusCode = 400;
|
|
238
|
+
res.end("Missing code or state");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (state !== expectedState) {
|
|
242
|
+
res.statusCode = 400;
|
|
243
|
+
res.end("State mismatch \u2014 request rejected.");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
res.statusCode = 200;
|
|
247
|
+
res.end("Authentication successful. You can close this tab and return to the terminal.");
|
|
248
|
+
resolveCode?.({ code, state });
|
|
249
|
+
});
|
|
250
|
+
server.listen(0, "127.0.0.1");
|
|
251
|
+
const timeoutHandle = setTimeout(() => {
|
|
252
|
+
rejectCode?.(new ConfigurationError("OAuth flow timed out", { code: "oauth_timeout" }));
|
|
253
|
+
}, timeoutMs);
|
|
254
|
+
await new Promise((r) => {
|
|
255
|
+
if (server.listening) r();
|
|
256
|
+
else server.once("listening", () => r());
|
|
257
|
+
});
|
|
258
|
+
const address = server.address();
|
|
259
|
+
if (typeof address !== "object" || address === null) {
|
|
260
|
+
server.close();
|
|
261
|
+
throw new ConfigurationError("OAuth localhost callback failed to bind", {
|
|
262
|
+
code: "oauth_bind_failed"
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const port = address.port;
|
|
266
|
+
return {
|
|
267
|
+
redirectUri: `http://127.0.0.1:${port}/callback`,
|
|
268
|
+
waitFor,
|
|
269
|
+
close: () => {
|
|
270
|
+
clearTimeout(timeoutHandle);
|
|
271
|
+
server.close();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
async function readCodeFromStdin(expectedState, timeoutMs) {
|
|
276
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
277
|
+
const promise = new Promise((resolve, reject) => {
|
|
278
|
+
rl.question("Paste the full callback URL (or just the code): ").then((answer) => {
|
|
279
|
+
rl.close();
|
|
280
|
+
const trimmed = answer.trim();
|
|
281
|
+
if (trimmed.includes("?")) {
|
|
282
|
+
try {
|
|
283
|
+
const parsed = new URL(trimmed);
|
|
284
|
+
const code = parsed.searchParams.get("code");
|
|
285
|
+
const state = parsed.searchParams.get("state") ?? "";
|
|
286
|
+
if (typeof code === "string") {
|
|
287
|
+
resolve({ code, state });
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
} catch {
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
resolve({ code: trimmed, state: expectedState });
|
|
294
|
+
}).catch(reject);
|
|
295
|
+
setTimeout(() => {
|
|
296
|
+
rl.close();
|
|
297
|
+
reject(new ConfigurationError("OAuth manual input timed out", { code: "oauth_timeout" }));
|
|
298
|
+
}, timeoutMs);
|
|
299
|
+
});
|
|
300
|
+
return promise;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export { getTokens, lockedRefresh, refreshAccessToken, runPkceFlow, setTokens };
|
|
304
|
+
//# sourceMappingURL=chunk-X7CE7Y5W.js.map
|
|
305
|
+
//# sourceMappingURL=chunk-X7CE7Y5W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":[],"mappings":"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","file":"chunk-X7CE7Y5W.js","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * The OAuth tokens held for one MCP server. See ADR D41.\n *\n * Persisted per server name by `setTokens` and returned unchanged by `getTokens`. Where it lands\n * depends on what is installed: `keytar` puts the serialized bundle in the OS keychain, and\n * without it the bundle is written to a JSON file at mode 0600 inside a 0700 directory — in\n * PLAINTEXT, keyed by server name alongside every other server's tokens. Nothing here encrypts it.\n *\n * `refreshToken` is the field that matters most and the field that is optional. When present it\n * outlives `accessToken` and can mint new ones, so a leaked store is a persistent compromise of\n * the account rather than a temporary one. When absent, an expired access token means re-running\n * the authorization flow.\n *\n * `expiresAt` and `obtainedAt` are both epoch milliseconds and both describe the ACCESS token —\n * when it stops working, and when this bundle was issued. Nothing records when the refresh token\n * expires, so a refresh rejected because the grant itself lapsed is only discovered at the token\n * endpoint.\n *\n * The value is not validated on read: whatever JSON was stored is cast to this shape, so a\n * hand-edited or truncated store yields an object whose fields may be missing rather than an\n * error.\n *\n * `scope` records what the token was granted, as the server returned it. It is stored for\n * inspection; nothing in this module enforces it.\n *\n * @public — re-exported from `@theokit/sdk/mcp-auth`, and therefore under semver.\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\n/**\n * Resolve the store path on every call instead of binding it once at module load.\n *\n * This used to be `const FILE_PATH = join(homedir(), ...)`. A module-level constant captures\n * ambient global state at IMPORT, so the store kept writing to whichever HOME was set when the\n * module first loaded and never noticed a later change. In production that is invisible, because\n * HOME does not move mid-process — but it made the module's correctness a property of *when* it was\n * imported, which is not a property a credential store should have.\n *\n * The environment variable is read FIRST and `homedir()` is the fallback, and the variable READ IS\n * PER PLATFORM because `os.homedir()` itself is: on POSIX it prefers `$HOME`, on Windows it reads\n * `USERPROFILE` and never consults `HOME`. Mirroring that split is what keeps this a pure\n * binding-time fix instead of a behaviour change.\n *\n * A previous revision read `process.env.HOME` on every platform. Review caught it: under Git Bash,\n * MSYS2 or Cygwin `HOME` IS set, to a POSIX-shaped path, and `path.win32.join(\"/c/Users/N\", ...)`\n * yields `\\c\\Users\\N\\.theokit\\...` where `USERPROFILE` would have given\n * `C:\\Users\\N\\.theokit\\...`. That would have hidden every existing token on those setups and\n * written a new credential file to a drive-relative path. It was published as \"on Windows HOME is\n * typically unset\" — an untested platform claim, and the same defect class it was written to fix.\n *\n * Why read the env at all: inside a worker thread `process.env` is a JS-level copy while\n * `os.homedir()` is a native call reading the real process environment, so code that moves the home\n * inside a worker is invisible to `homedir()`. Reading the env first makes this module independent\n * of the execution model, not just of the import moment.\n *\n * The empty/whitespace guard falls through to `homedir()`, and it buys LESS than two earlier\n * revisions of this comment claimed. Measured on POSIX: `homedir()` with `HOME=\"\"` returns `\"\"` and\n * with `HOME=\" \"` returns `\" \"` untrimmed, so the fallback hands back the same value the guard\n * rejected — close to a no-op. `path.join(\"\", \".theokit\", ...)` yields a CWD-RELATIVE\n * `.theokit/mcp-tokens.json`, never `/.theokit`; an earlier revision named that as the hazard and it\n * does not exist.\n *\n * On Windows it is UNKNOWN rather than load-bearing: the fallback reads the same `USERPROFILE`, so\n * whether the guard changes anything depends on whether an empty variable is reported as absent\n * there, which is untested and not asserted here. The one case where the guard is genuinely\n * load-bearing is a worker thread whose environment copy was blanked.\n *\n * The OS is UNTESTED here: every POSIX-mode test in `mcp/token-store-modes.test.ts` is\n * `it.skipIf(!POSIX)` and CI runs ubuntu only, so nothing exercises real Windows chmod semantics or\n * libuv's `USERPROFILE` lookup. The BRANCH SELECTION is tested — `process.platform` is spy-able, and\n * `test_the_store_reads_USERPROFILE_and_not_HOME_on_win32` pins all three legs.\n *\n * The split is reasoned from `os.homedir()`'s documented per-platform source. A previous revision\n * also cited `internal/runtime/fixtures/fixture-mode.ts:119` as support; that citation was WRONG and\n * is removed rather than quietly dropped. That line reads `process.env.HOME ?? process.env.USERPROFILE`\n * — HOME-first on every platform, which is the exact shape this split exists to avoid. It supports\n * the rejected alternative, not this one. (That the two modules now resolve different homes under\n * Git Bash is real and is filed separately; `fixture-mode` only probes for `~/.aws/credentials`.)\n *\n * Measured cost, not asserted: `homedir()` is 151 ns/op against 13 382 for the read this path\n * performs and 97 079 for the write (89x and 645x). The resolution is free relative to the I/O it\n * precedes.\n *\n * B-089. `packages/sdk/tests/mcp/token-store-modes.test.ts` pins it: import once, move HOME, write,\n * assert the write followed — and it must hold under `--pool=threads`, not only under the repo's\n * configured `forks`.\n *\n * B-090 — `THEOKIT_HOME` now WINS over the home-anchored default, and the reason is not tidiness.\n * `vitest.setup.ts` isolates every test in this package by pointing `THEOKIT_HOME` at a fresh\n * tmpdir; it backs `HOME` up and never sets it. A home-anchored module that ignored the variable\n * therefore resolved to the developer's REAL `~` while the suite believed it was isolated — and it\n * did: a default-config run deposited four refresh-token entries (`test-srv`, `srv-2`, `srv-race`,\n * `srv-roundtrip`) into `~/.theokit/mcp-tokens.json` from `tests/golden/mcp/oauth.golden.test.ts`.\n * A suite that is wrong about its own isolation is a false green about the one property the rest of\n * its greens rest on.\n *\n * **This is the code catching up to a contract the SDK already published**, not a new policy.\n * `src/project-env.ts:47-49` documents `THEOKIT_HOME` as \"Locates the SDK home — sessions, AND THE\n * CREDENTIAL STORE BENEATH IT\", and it is listed there as a sovereign key precisely because it\n * governs where credentials live. The public contract already said the store sits under the\n * variable; this module was the half that disagreed.\n *\n * The resolver adopted is `transcriptRoot()`'s (`internal/persistence/session-transcript.ts`), NOT\n * `paths.ts`'s `getTheokitHome(cwd)`. The transcript is the sibling with the right SHAPE: home-anchored\n * default, `THEOKIT_HOME` override, trimmed, empty-guarded — and its docstring records that before M94\n * it ignored the variable, so \"whoever set it had their state split in two silently\", which is this\n * defect verbatim. M94 ADR-2 already accepted that migration for identically-shaped state.\n * `getTheokitHome(cwd)` falls back to `<cwd>/.theokit`, so adopting IT would move the token file of\n * everyone who does not set the variable — and to a DIFFERENT place per working directory, making\n * whether you are logged in a property of which folder you launched from.\n *\n * **The migration this does carry**, stated rather than buried: a user who already holds\n * `~/.theokit/mcp-tokens.json` AND sets `THEOKIT_HOME` stops seeing those tokens. `getTokens` returns\n * `undefined`, which the caller surfaces as \"not logged in\", and the OAuth flow re-runs. Nothing is\n * deleted and nothing is overwritten — the old file stays where it is and is picked up again the\n * moment the variable is unset. No migration step is performed on the user's behalf, because\n * silently moving a credential file is a worse failure than a re-auth. Users who do NOT set\n * `THEOKIT_HOME` — the default — see no change at all.\n *\n * **Two further consequences for those who DO set it**, both on the directory-mode path rather than\n * on the path resolution, and both spelled out at {@link ensurePrivateStoreDir}: the store no longer\n * re-permissions a `$THEOKIT_HOME` it did not create, and `getTokens` REFUSES — typed, loud — to read\n * out of a group- or world-writable one instead of returning `undefined`.\n */\nfunction storeFilePath(): string {\n return storeLocation().file;\n}\n\n/**\n * Where the store lives, and whether that directory is OURS.\n *\n * `ours` is not bookkeeping — it decides whether this module is entitled to change the directory's\n * permissions. `<home>/.theokit` is a directory the SDK creates and owns; a `THEOKIT_HOME` root is\n * one the operator chose and may share with other state. See {@link ensurePrivateStoreDir}.\n */\ninterface StoreLocation {\n readonly dir: string;\n readonly file: string;\n readonly ours: boolean;\n}\n\n/**\n * The directory the token file lives in: `THEOKIT_HOME` when usable, `<home>/.theokit` otherwise.\n *\n * The empty/whitespace guard on the override is load-bearing in a way the HOME guard below is not:\n * `THEOKIT_HOME=\"\"` with no guard resolves the store to a CWD-RELATIVE `mcp-tokens.json`, and\n * `THEOKIT_HOME=\" \"` to a directory literally named three spaces. Neither falls back to anything;\n * both are new locations invented from an unusable value. `transcriptRoot()` guards it for the same\n * reason.\n */\nfunction storeLocation(): StoreLocation {\n const override = process.env.THEOKIT_HOME?.trim();\n if (override !== undefined && override.length > 0) {\n return { dir: override, file: join(override, \"mcp-tokens.json\"), ours: false };\n }\n // Mirrors `os.homedir()`'s own per-platform source: USERPROFILE on Windows, HOME elsewhere.\n const fromEnv =\n process.platform === \"win32\" ? process.env.USERPROFILE?.trim() : process.env.HOME?.trim();\n const home = fromEnv !== undefined && fromEnv.length > 0 ? fromEnv : homedir();\n const dir = join(home, \".theokit\");\n return { dir, file: join(dir, \"mcp-tokens.json\"), ours: true };\n}\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * **The retro-fix `chmod` is scoped to directories we own, and B-090 is why.** It was written\n * unconditional for a specific reason: `mkdir`'s mode applies only at CREATION, so every machine\n * that already ran an older build has a loose `~/.theokit` sitting there, and a fix that only covers\n * fresh installs does not reach the population that has the problem. That reasoning names its own\n * population — directories THIS MODULE created in an older build. Once `THEOKIT_HOME` is honoured,\n * an unconditional `chmod` also reaches a root the operator chose, which we never created and which\n * `paths.ts` documents as a multi-tenant deployment knob. Measured: it silently demoted a 0775\n * `$THEOKIT_HOME` to 0700, taking the sessions, transcripts, personality and credential-pool state\n * that share that root with it. No other consumer of the variable imposes a mode on it.\n *\n * So: always `mkdir` at 0700 (a directory WE create is born private wherever it is), and `chmod` an\n * existing one only when it is `<home>/.theokit`.\n *\n * The `chmod` deliberately does NOT also cover \"we just created it\". A first draft guarded it with\n * `ours || created !== undefined`; mutating that to plain `ours` killed no test, and the reason is\n * that the disjunct cannot make a difference: a umask only CLEARS bits, and 0700 carries none in the\n * group/other range, so `mkdir(0700)` is private for every possible umask. The redundant clause was\n * removed rather than pinned with a test written to justify it.\n *\n * **What that leaves, stated rather than left to be discovered.** A pre-existing group-writable\n * `$THEOKIT_HOME` is no longer repaired, so the next {@link getTokens} throws `CredentialError` from\n * `assertSecureModes` naming the directory and the `chmod 700` that fixes it. That is the intended\n * end state: a refresh token under a directory any local user can replace files in is not safe to\n * return, and the two available alternatives are worse. Silently tightening the operator's root\n * breaks their deployment to protect them from a choice they may have made deliberately; silently\n * returning the token hands the caller a credential that may already have been swapped. A loud,\n * actionable refusal is the only option that neither mutates someone else's directory nor lies about\n * what it read.\n *\n * The asymmetry that remains is real and is NOT fixed here: the write path has no such gate, so\n * `setTokens` will happily write into that directory and the following `getTokens` refuses it.\n * Adding a write-side gate is a separate behaviour change on a path that currently always succeeds.\n */\nfunction ensurePrivateStoreDir({ dir, ours }: StoreLocation): void {\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n if (ours) chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n // The path is RESOLVED rather than spelled `~/.theokit/mcp-tokens.json`. That literal was\n // correct until B-090 made `THEOKIT_HOME` win, at which point a message naming a fixed\n // location would send anyone who sets the variable to look at a file the store no longer\n // writes — a diagnostic that lies costs more than one that is absent.\n diag(\n `[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ${storeFilePath()} (chmod 600 where POSIX). Install \\`keytar\\` for OS keychain storage.\\n`,\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n // Resolved once for the whole operation and PASSED DOWN. Resolving per use would let a read and\n // the write that follows it disagree if HOME moved in between — and `ensurePrivateStoreDir`\n // resolving its own copy would let the directory be locked down under one home while the token\n // lands under another. Today those two statements are adjacent and synchronous so they cannot\n // diverge, but that is incidental; passing the path makes the invariant structural.\n const location = storeLocation();\n const filePath = location.file;\n ensurePrivateStoreDir(location);\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(filePath)) {\n try {\n allTokens = JSON.parse(readFileSync(filePath, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(filePath, allTokens);\n try {\n chmodSync(filePath, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n const filePath = storeFilePath();\n if (!existsSync(filePath)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(filePath), filePath);\n try {\n const all = JSON.parse(readFileSync(filePath, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var chunkGHX4P3V2_cjs = require('./chunk-GHX4P3V2.cjs');
|
|
4
|
-
var
|
|
4
|
+
var chunk3F53FLQ7_cjs = require('./chunk-3F53FLQ7.cjs');
|
|
5
5
|
var chunkQOGNZMHD_cjs = require('./chunk-QOGNZMHD.cjs');
|
|
6
6
|
var chunk27V5I2WW_cjs = require('./chunk-27V5I2WW.cjs');
|
|
7
7
|
var chunkEHD25JBZ_cjs = require('./chunk-EHD25JBZ.cjs');
|
|
@@ -138,7 +138,7 @@ async function embedBatch(opts) {
|
|
|
138
138
|
if (isRetryable(response.status) && attempt < MAX_RETRIES) {
|
|
139
139
|
attempt += 1;
|
|
140
140
|
opts.stats.retries += 1;
|
|
141
|
-
const retryAfterSec =
|
|
141
|
+
const retryAfterSec = chunk3F53FLQ7_cjs.parseRetryAfter(response.headers);
|
|
142
142
|
await sleep(
|
|
143
143
|
chunkQOGNZMHD_cjs.computeBackoffMs({
|
|
144
144
|
attempt,
|
|
@@ -159,7 +159,7 @@ async function mappedEmbedError(opts, response) {
|
|
|
159
159
|
body = JSON.parse(text);
|
|
160
160
|
} catch {
|
|
161
161
|
}
|
|
162
|
-
return
|
|
162
|
+
return chunk3F53FLQ7_cjs.mapOpenAICompatibleError({
|
|
163
163
|
providerId: opts.providerId,
|
|
164
164
|
status: response.status,
|
|
165
165
|
body,
|
|
@@ -207,5 +207,5 @@ function sleep(ms) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
exports.createOpenAiCompatibleRuntime = createOpenAiCompatibleRuntime;
|
|
210
|
-
//# sourceMappingURL=chunk-
|
|
211
|
-
//# sourceMappingURL=chunk-
|
|
210
|
+
//# sourceMappingURL=chunk-XE3CAY6Z.cjs.map
|
|
211
|
+
//# sourceMappingURL=chunk-XE3CAY6Z.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/memory/adapters/openai-compatible.ts"],"names":["AuthenticationError","boundFetch","globalEmbeddingCache","ConfigurationError","mapWithConcurrency","parseRetryAfter","computeBackoffMs","mapOpenAICompatibleError","NetworkError","createHash"],"mappings":"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","file":"chunk-CGH4BCIY.cjs","sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport { AuthenticationError, ConfigurationError, NetworkError } from \"../../../errors.js\";\nimport { mapWithConcurrency } from \"../../concurrency/map-with-concurrency.js\";\nimport { mapOpenAICompatibleError } from \"../../error-mappers/openai-compatible.js\";\nimport { parseRetryAfter } from \"../../error-mappers/shared.js\";\nimport { computeBackoffMs } from \"../../llm/retry.js\";\nimport { boundFetch } from \"../../runtime-fetch.js\";\nimport type {\n CreateAdapterOptions,\n EmbeddingRuntime,\n EmbeddingRuntimeStats,\n} from \"../embedding-adapter.js\";\nimport { globalEmbeddingCache } from \"../embedding-cache.js\";\n\n/**\n * Shared factory for OpenAI-compatible embedding providers (OpenAI, Mistral,\n * DeepInfra, and anything else exposing `POST /v1/embeddings` with the\n * `{ model, input }` request shape and `{ data: [{ embedding }] }` response).\n *\n * Mirrors the inner mechanics of peer-project's batch embedding runtime.\n *\n * @internal\n */\n\nconst MAX_BATCH = 100;\nconst MAX_RETRIES = 2;\n/**\n * Deliberately tighter than the transport's 500ms/32s.\n *\n * An embedding retry sits inside a memory write on the agent's critical path and cannot be\n * cancelled (see `sleep` below), so the worst case has to be small enough to wait out: 2 retries\n * capped at 4s is at most ~8s. The transport can afford 32s because a streaming turn is already\n * long-running and IS abortable.\n */\nconst EMBED_BACKOFF_BASE_MS = 250;\nconst EMBED_BACKOFF_CAP_MS = 4_000;\n\n/**\n * What one provider adapter tells {@link createOpenAiCompatibleRuntime} about\n * its wire: where to POST, which environment variables carry the key and the\n * base URL, which model to use by default, and how wide each model's vectors\n * are.\n *\n * `id` is the provider id that ends up on errors and on\n * `EmbeddingRuntime.id`, and is also the identity the SQLite vector index stores\n * — changing it invalidates the vectors already on disk.\n *\n * `apiKeyEnv` is required, `baseUrlEnv` is not: a provider that declares no\n * base-URL variable can only be redirected by passing `baseUrl` explicitly.\n * Leave `dialect` unset for anything that speaks the OpenAI embedding wire.\n */\nexport interface OpenAiCompatibleConfig {\n id: string;\n defaultBaseUrl: string;\n apiKeyEnv: string;\n baseUrlEnv?: string;\n defaultModel: string;\n /**\n * Dimension hint by model id. The chosen model MUST be in this table —\n * unknown models throw `ConfigurationError(code: \"embedding_unknown_model\")`\n * (EC-4 fix). This prevents vec0 virtual-table dimension mismatches that\n * surface as cryptic errors downstream.\n */\n dimensionByModel: Record<string, number>;\n /**\n * Path component appended to `baseUrl` for the embeddings endpoint.\n * Default `\"/v1/embeddings\"` (OpenAI canonical). REPLACES the default —\n * does NOT concatenate. Used by DeepInfra (`\"/v1/openai/embeddings\"`),\n * etc. (EC-2 fix).\n */\n embeddingsPath?: string;\n /**\n * theokit#159 — per-provider deviations from the OpenAI wire.\n *\n * The runtime was rigidly OpenAI-shaped: one auth header, one body, one response shape. Three of\n * the ten catalog providers do not speak it, so every call they made was rejected — Azure\n * authenticates with an `api-key` header and puts the deployment in the path, Cohere's `/v2/embed`\n * takes `texts` and answers `{embeddings:{float}}`. Advertising a provider that cannot work is\n * worse than not advertising it.\n *\n * Every hook is optional and its default is exactly the previous behaviour, so the seven\n * providers that DO speak OpenAI are untouched.\n */\n dialect?: EmbeddingDialect;\n}\n\n/**\n * theokit#159 — the three points where a provider may deviate from the OpenAI embedding wire.\n *\n * Deliberately three narrow hooks rather than a general \"transform the request\" seam: these are the\n * only axes on which the supported providers actually differ, and a wider seam would invite\n * per-provider logic that belongs in the adapter.\n */\nexport interface EmbeddingDialect {\n /** Replaces `authorization: Bearer <key>`. */\n authHeaders?: (apiKey: string) => Record<string, string>;\n /** Replaces the `{ model, input }` request body. */\n body?: (model: string, inputs: ReadonlyArray<string>) => Record<string, unknown>;\n /** Replaces reading vectors from `json.data[].embedding`. Return `undefined` when unparseable. */\n vectors?: (json: unknown) => number[][] | undefined;\n}\n\n/**\n * Build an {@link EmbeddingRuntime} for a provider that speaks the OpenAI\n * embedding wire — or, through `cfg.dialect`, one that deviates from it in a\n * known way. Every adapter in the memory catalog is a thin call to this.\n *\n * Resolution order for each setting is explicit option, then environment\n * variable, then the config default. Two things are checked before any request\n * is sent, so a misconfiguration fails at creation rather than at first search:\n * a missing or empty key rejects with an `AuthenticationError` carrying\n * `embedding_missing_api_key`, and a model absent from `dimensionByModel`\n * rejects with a `ConfigurationError` carrying `embedding_unknown_model`. The\n * second check exists because the vector index is created at a fixed width — an\n * unknown dimension would surface much later as a vec0 mismatch.\n *\n * `{model}` in `embeddingsPath` is replaced with the URL-encoded model, which is\n * how Azure addresses a deployment in the path. `embeddingsPath` replaces the\n * default `/v1/embeddings` rather than being appended to it.\n *\n * The returned `embed` batches at 100 texts per request and runs at most 3\n * requests concurrently, preserving input order. Whitespace-only inputs never\n * reach the network and come back as an all-zero vector. Results are cached by\n * model and text; the cache defaults to a process-wide LRU of 5000 entries, so\n * pass `options.cache` when runtimes must not share entries.\n *\n * Failed requests retry up to twice on 429 and 5xx with a 50ms-per-attempt\n * linear backoff. Anything else, and a retry budget that runs out, throws the\n * typed error the OpenAI-compatible mapper builds from the status and body. A\n * response that parses but carries no vectors throws a `NetworkError` with\n * `embedding_invalid_response`. There is no request timeout here — cancellation\n * is the caller's to impose.\n */\nexport async function createOpenAiCompatibleRuntime(\n cfg: OpenAiCompatibleConfig,\n options: CreateAdapterOptions,\n): Promise<EmbeddingRuntime> {\n const model = options.model ?? cfg.defaultModel;\n const apiKey = options.apiKey ?? process.env[cfg.apiKeyEnv];\n if (apiKey === undefined || apiKey.length === 0) {\n throw new AuthenticationError(`${cfg.apiKeyEnv} missing`, {\n code: \"embedding_missing_api_key\",\n });\n }\n const envBaseUrl = cfg.baseUrlEnv !== undefined ? process.env[cfg.baseUrlEnv] : undefined;\n const baseUrl = options.baseUrl ?? envBaseUrl ?? cfg.defaultBaseUrl;\n const fetchImpl = boundFetch(options.fetch);\n // T4.4 — default to process-wide singleton (was per-adapter instance).\n const cache = options.cache ?? globalEmbeddingCache;\n // EC-4: refuse unknown models to prevent vec0 dimension mismatches downstream.\n const dimension = cfg.dimensionByModel[model];\n if (dimension === undefined) {\n throw new ConfigurationError(\n `${cfg.id} adapter: model \"${model}\" is not in the dimension table. Use one of: ${Object.keys(cfg.dimensionByModel).join(\", \")}. To add it, update dimensionByModel in the adapter source.`,\n { code: \"embedding_unknown_model\" },\n );\n }\n // theokit#128: `{model}` is substituted, because Azure OpenAI addresses the deployment IN THE\n // PATH (`/openai/deployments/{model}/embeddings`) rather than in the body. Without this the URL\n // carried the literal placeholder and every Azure request 404'd. A path with no placeholder —\n // every other provider — is passed through untouched.\n const embeddingsPath = (cfg.embeddingsPath ?? \"/v1/embeddings\").replace(\n \"{model}\",\n encodeURIComponent(model),\n );\n\n const stats: EmbeddingRuntimeStats = {\n cacheHits: 0,\n cacheMisses: 0,\n httpCalls: 0,\n retries: 0,\n };\n\n return {\n id: cfg.id,\n model,\n dimension,\n stats: () => ({ ...stats }),\n embed: (texts) =>\n embedTexts({\n texts,\n cache,\n stats,\n model,\n dimension,\n apiKey,\n baseUrl,\n embeddingsPath,\n fetchImpl,\n providerId: cfg.id,\n dialect: cfg.dialect ?? {},\n }),\n };\n}\n\ninterface EmbedTextsInput {\n texts: ReadonlyArray<string>;\n cache: NonNullable<CreateAdapterOptions[\"cache\"]>;\n stats: EmbeddingRuntimeStats;\n model: string;\n dimension: number;\n apiKey: string;\n baseUrl: string;\n embeddingsPath: string;\n fetchImpl: typeof fetch;\n providerId: string;\n dialect: EmbeddingDialect;\n}\n\nasync function embedTexts(input: EmbedTextsInput): Promise<number[][]> {\n const { texts, cache, stats, model, dimension } = input;\n const results = new Array<number[] | undefined>(texts.length);\n const pending: Array<{ index: number; text: string; key: string }> = [];\n for (let i = 0; i < texts.length; i++) {\n classifyEntry({\n index: i,\n text: texts[i] ?? \"\",\n model,\n dimension,\n cache,\n stats,\n results,\n pending,\n });\n }\n await embedInBoundedBatches(input, pending, results);\n return results.map((v) => v ?? new Array(dimension).fill(0));\n}\n\ninterface ClassifyEntryArgs {\n index: number;\n text: string;\n model: string;\n dimension: number;\n cache: NonNullable<CreateAdapterOptions[\"cache\"]>;\n stats: EmbeddingRuntimeStats;\n results: Array<number[] | undefined>;\n pending: Array<{ index: number; text: string; key: string }>;\n}\n\nfunction classifyEntry(args: ClassifyEntryArgs): void {\n if (args.text.trim().length === 0) {\n args.results[args.index] = new Array(args.dimension).fill(0);\n return;\n }\n const key = hashKey(args.model, args.text);\n const cached = args.cache.get(key);\n if (cached !== undefined) {\n args.stats.cacheHits += 1;\n args.results[args.index] = cached;\n return;\n }\n args.stats.cacheMisses += 1;\n args.pending.push({ index: args.index, text: args.text, key });\n}\n\n// T4.3 — max concurrent embedding API requests per embed() call.\n// 3 is enough to saturate most embedding APIs without triggering\n// rate limits (each batch is already up to MAX_BATCH=100 texts).\nconst MAX_CONCURRENT_BATCHES = 3;\n\n/**\n * T4.3 — parallel embed batches (DR4 finding #3).\n *\n * Pre-T4.3 this was a serial `for` loop — each batch of 100 texts\n * awaited before the next HTTP request started. With 500 texts the\n * serial path took 5 × RTT; parallel takes max(RTT) × ceil(5/3).\n *\n * Uses bounded-concurrency `Promise.all` capped at 3 concurrent\n * HTTP requests to avoid overwhelming the embedding API.\n */\nasync function embedInBoundedBatches(\n input: EmbedTextsInput,\n pending: ReadonlyArray<{ index: number; text: string; key: string }>,\n results: Array<number[] | undefined>,\n): Promise<void> {\n const batches: Array<ReadonlyArray<{ index: number; text: string; key: string }>> = [];\n for (let offset = 0; offset < pending.length; offset += MAX_BATCH) {\n batches.push(pending.slice(offset, offset + MAX_BATCH));\n }\n // M0-2: bounded parallel via the shared ordered pool (was an inline\n // acquire/release clone — see plan m0-foundation-expose-primitives).\n await mapWithConcurrency(batches, MAX_CONCURRENT_BATCHES, (batch) =>\n processBatch(input, batch, results),\n );\n}\n\nasync function processBatch(\n input: EmbedTextsInput,\n batch: ReadonlyArray<{ index: number; text: string; key: string }>,\n results: Array<number[] | undefined>,\n): Promise<void> {\n const vectors = await embedBatch({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n embeddingsPath: input.embeddingsPath,\n model: input.model,\n inputs: batch.map((b) => b.text),\n fetchImpl: input.fetchImpl,\n stats: input.stats,\n providerId: input.providerId,\n dialect: input.dialect,\n });\n for (let j = 0; j < batch.length; j++) {\n const slot = batch[j];\n const vector = vectors[j];\n if (slot === undefined || vector === undefined) continue;\n results[slot.index] = vector;\n input.cache.set(slot.key, vector);\n }\n}\n\ninterface BatchOptions {\n apiKey: string;\n baseUrl: string;\n embeddingsPath: string;\n model: string;\n inputs: ReadonlyArray<string>;\n fetchImpl: typeof fetch;\n stats: EmbeddingRuntimeStats;\n providerId: string;\n dialect: EmbeddingDialect;\n}\n\nasync function embedBatch(opts: BatchOptions): Promise<number[][]> {\n // EC-2: embeddingsPath REPLACES the suffix; never concatenates.\n const url = `${opts.baseUrl.replace(/\\/$/, \"\")}${opts.embeddingsPath}`;\n let attempt = 0;\n while (true) {\n opts.stats.httpCalls += 1;\n const response = await postEmbedRequest(opts, url);\n if (response.ok)\n return await parseEmbedResponse(response, opts.providerId, opts.embeddingsPath, opts.dialect);\n if (isRetryable(response.status) && attempt < MAX_RETRIES) {\n attempt += 1;\n opts.stats.retries += 1;\n const retryAfterSec = parseRetryAfter(response.headers);\n await sleep(\n computeBackoffMs({\n attempt,\n baseMs: EMBED_BACKOFF_BASE_MS,\n capMs: EMBED_BACKOFF_CAP_MS,\n ...(retryAfterSec !== undefined ? { retryAfterMs: retryAfterSec * 1000 } : {}),\n }),\n );\n continue;\n }\n throw await mappedEmbedError(opts, response);\n }\n}\n\n/**\n * The terminal failure: read the body best-effort so the mapper sees the provider's error code, then\n * map it. Its own function because folding it back into the retry loop puts `embedBatch` at\n * cognitive complexity 11 against the repo max of 10 — and the sibling budget for those suppressions\n * is ratcheting DOWN, so adding one here to keep this loop whole would undo that.\n */\nasync function mappedEmbedError(opts: BatchOptions, response: Response): Promise<unknown> {\n const text = await response.text().catch(() => \"\");\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n // not JSON — keep as string\n }\n return mapOpenAICompatibleError({\n providerId: opts.providerId,\n status: response.status,\n body,\n headers: response.headers,\n endpoint: opts.embeddingsPath,\n });\n}\n\nasync function postEmbedRequest(opts: BatchOptions, url: string): Promise<Response> {\n return opts.fetchImpl(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(opts.dialect.authHeaders?.(opts.apiKey) ?? { authorization: `Bearer ${opts.apiKey}` }),\n },\n body: JSON.stringify(\n opts.dialect.body?.(opts.model, opts.inputs) ?? { model: opts.model, input: opts.inputs },\n ),\n });\n}\n\nasync function parseEmbedResponse(\n response: Response,\n providerId: string,\n endpoint: string,\n dialect?: EmbeddingDialect,\n): Promise<number[][]> {\n const json = (await response.json()) as { data?: Array<{ embedding: number[] }> };\n // theokit#159 — a provider whose response is not OpenAI-shaped supplies its own reader.\n const viaDialect = dialect?.vectors?.(json);\n if (viaDialect !== undefined) return viaDialect;\n if (!Array.isArray(json.data)) {\n throw new NetworkError(`${providerId} ${endpoint} returned no data`, {\n code: \"embedding_invalid_response\",\n metadata: {\n provider: providerId,\n endpoint,\n code: \"invalid_request\",\n raw: json,\n },\n });\n }\n return json.data.map((d) => d.embedding);\n}\n\nfunction isRetryable(status: number): boolean {\n return status === 429 || (status >= 500 && status < 600);\n}\n\nfunction hashKey(model: string, text: string): string {\n return createHash(\"sha256\").update(`${model} ${text}`).digest(\"hex\");\n}\n\n/**\n * Not `sleepWithAbort`, and the reason is the parsimony ladder rather than an oversight.\n *\n * `EmbeddingRuntime.embed(texts)` takes no signal, and NONE of its seven callers — lance-index,\n * index-manager, vec-index, the two dreaming phases, scorers — holds one to pass. Adding the\n * parameter would add a seam nobody supplies, so cancellation is bounded instead of plumbed: at most\n * MAX_RETRIES sleeps of at most EMBED_BACKOFF_CAP_MS each. Give `embed()` a signal on the day a\n * caller has one, and this becomes `sleepWithAbort` in the same edit.\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/memory/adapters/openai-compatible.ts"],"names":["AuthenticationError","boundFetch","globalEmbeddingCache","ConfigurationError","mapWithConcurrency","parseRetryAfter","computeBackoffMs","mapOpenAICompatibleError","NetworkError","createHash"],"mappings":"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","file":"chunk-XE3CAY6Z.cjs","sourcesContent":["import { createHash } from \"node:crypto\";\n\nimport { AuthenticationError, ConfigurationError, NetworkError } from \"../../../errors.js\";\nimport { mapWithConcurrency } from \"../../concurrency/map-with-concurrency.js\";\nimport { mapOpenAICompatibleError } from \"../../error-mappers/openai-compatible.js\";\nimport { parseRetryAfter } from \"../../error-mappers/shared.js\";\nimport { computeBackoffMs } from \"../../llm/retry.js\";\nimport { boundFetch } from \"../../runtime-fetch.js\";\nimport type {\n CreateAdapterOptions,\n EmbeddingRuntime,\n EmbeddingRuntimeStats,\n} from \"../embedding-adapter.js\";\nimport { globalEmbeddingCache } from \"../embedding-cache.js\";\n\n/**\n * Shared factory for OpenAI-compatible embedding providers (OpenAI, Mistral,\n * DeepInfra, and anything else exposing `POST /v1/embeddings` with the\n * `{ model, input }` request shape and `{ data: [{ embedding }] }` response).\n *\n * Mirrors the inner mechanics of peer-project's batch embedding runtime.\n *\n * @internal\n */\n\nconst MAX_BATCH = 100;\nconst MAX_RETRIES = 2;\n/**\n * Deliberately tighter than the transport's 500ms/32s.\n *\n * An embedding retry sits inside a memory write on the agent's critical path and cannot be\n * cancelled (see `sleep` below), so the worst case has to be small enough to wait out: 2 retries\n * capped at 4s is at most ~8s. The transport can afford 32s because a streaming turn is already\n * long-running and IS abortable.\n */\nconst EMBED_BACKOFF_BASE_MS = 250;\nconst EMBED_BACKOFF_CAP_MS = 4_000;\n\n/**\n * What one provider adapter tells {@link createOpenAiCompatibleRuntime} about\n * its wire: where to POST, which environment variables carry the key and the\n * base URL, which model to use by default, and how wide each model's vectors\n * are.\n *\n * `id` is the provider id that ends up on errors and on\n * `EmbeddingRuntime.id`, and is also the identity the SQLite vector index stores\n * — changing it invalidates the vectors already on disk.\n *\n * `apiKeyEnv` is required, `baseUrlEnv` is not: a provider that declares no\n * base-URL variable can only be redirected by passing `baseUrl` explicitly.\n * Leave `dialect` unset for anything that speaks the OpenAI embedding wire.\n */\nexport interface OpenAiCompatibleConfig {\n id: string;\n defaultBaseUrl: string;\n apiKeyEnv: string;\n baseUrlEnv?: string;\n defaultModel: string;\n /**\n * Dimension hint by model id. The chosen model MUST be in this table —\n * unknown models throw `ConfigurationError(code: \"embedding_unknown_model\")`\n * (EC-4 fix). This prevents vec0 virtual-table dimension mismatches that\n * surface as cryptic errors downstream.\n */\n dimensionByModel: Record<string, number>;\n /**\n * Path component appended to `baseUrl` for the embeddings endpoint.\n * Default `\"/v1/embeddings\"` (OpenAI canonical). REPLACES the default —\n * does NOT concatenate. Used by DeepInfra (`\"/v1/openai/embeddings\"`),\n * etc. (EC-2 fix).\n */\n embeddingsPath?: string;\n /**\n * theokit#159 — per-provider deviations from the OpenAI wire.\n *\n * The runtime was rigidly OpenAI-shaped: one auth header, one body, one response shape. Three of\n * the ten catalog providers do not speak it, so every call they made was rejected — Azure\n * authenticates with an `api-key` header and puts the deployment in the path, Cohere's `/v2/embed`\n * takes `texts` and answers `{embeddings:{float}}`. Advertising a provider that cannot work is\n * worse than not advertising it.\n *\n * Every hook is optional and its default is exactly the previous behaviour, so the seven\n * providers that DO speak OpenAI are untouched.\n */\n dialect?: EmbeddingDialect;\n}\n\n/**\n * theokit#159 — the three points where a provider may deviate from the OpenAI embedding wire.\n *\n * Deliberately three narrow hooks rather than a general \"transform the request\" seam: these are the\n * only axes on which the supported providers actually differ, and a wider seam would invite\n * per-provider logic that belongs in the adapter.\n */\nexport interface EmbeddingDialect {\n /** Replaces `authorization: Bearer <key>`. */\n authHeaders?: (apiKey: string) => Record<string, string>;\n /** Replaces the `{ model, input }` request body. */\n body?: (model: string, inputs: ReadonlyArray<string>) => Record<string, unknown>;\n /** Replaces reading vectors from `json.data[].embedding`. Return `undefined` when unparseable. */\n vectors?: (json: unknown) => number[][] | undefined;\n}\n\n/**\n * Build an {@link EmbeddingRuntime} for a provider that speaks the OpenAI\n * embedding wire — or, through `cfg.dialect`, one that deviates from it in a\n * known way. Every adapter in the memory catalog is a thin call to this.\n *\n * Resolution order for each setting is explicit option, then environment\n * variable, then the config default. Two things are checked before any request\n * is sent, so a misconfiguration fails at creation rather than at first search:\n * a missing or empty key rejects with an `AuthenticationError` carrying\n * `embedding_missing_api_key`, and a model absent from `dimensionByModel`\n * rejects with a `ConfigurationError` carrying `embedding_unknown_model`. The\n * second check exists because the vector index is created at a fixed width — an\n * unknown dimension would surface much later as a vec0 mismatch.\n *\n * `{model}` in `embeddingsPath` is replaced with the URL-encoded model, which is\n * how Azure addresses a deployment in the path. `embeddingsPath` replaces the\n * default `/v1/embeddings` rather than being appended to it.\n *\n * The returned `embed` batches at 100 texts per request and runs at most 3\n * requests concurrently, preserving input order. Whitespace-only inputs never\n * reach the network and come back as an all-zero vector. Results are cached by\n * model and text; the cache defaults to a process-wide LRU of 5000 entries, so\n * pass `options.cache` when runtimes must not share entries.\n *\n * Failed requests retry up to twice on 429 and 5xx with a 50ms-per-attempt\n * linear backoff. Anything else, and a retry budget that runs out, throws the\n * typed error the OpenAI-compatible mapper builds from the status and body. A\n * response that parses but carries no vectors throws a `NetworkError` with\n * `embedding_invalid_response`. There is no request timeout here — cancellation\n * is the caller's to impose.\n */\nexport async function createOpenAiCompatibleRuntime(\n cfg: OpenAiCompatibleConfig,\n options: CreateAdapterOptions,\n): Promise<EmbeddingRuntime> {\n const model = options.model ?? cfg.defaultModel;\n const apiKey = options.apiKey ?? process.env[cfg.apiKeyEnv];\n if (apiKey === undefined || apiKey.length === 0) {\n throw new AuthenticationError(`${cfg.apiKeyEnv} missing`, {\n code: \"embedding_missing_api_key\",\n });\n }\n const envBaseUrl = cfg.baseUrlEnv !== undefined ? process.env[cfg.baseUrlEnv] : undefined;\n const baseUrl = options.baseUrl ?? envBaseUrl ?? cfg.defaultBaseUrl;\n const fetchImpl = boundFetch(options.fetch);\n // T4.4 — default to process-wide singleton (was per-adapter instance).\n const cache = options.cache ?? globalEmbeddingCache;\n // EC-4: refuse unknown models to prevent vec0 dimension mismatches downstream.\n const dimension = cfg.dimensionByModel[model];\n if (dimension === undefined) {\n throw new ConfigurationError(\n `${cfg.id} adapter: model \"${model}\" is not in the dimension table. Use one of: ${Object.keys(cfg.dimensionByModel).join(\", \")}. To add it, update dimensionByModel in the adapter source.`,\n { code: \"embedding_unknown_model\" },\n );\n }\n // theokit#128: `{model}` is substituted, because Azure OpenAI addresses the deployment IN THE\n // PATH (`/openai/deployments/{model}/embeddings`) rather than in the body. Without this the URL\n // carried the literal placeholder and every Azure request 404'd. A path with no placeholder —\n // every other provider — is passed through untouched.\n const embeddingsPath = (cfg.embeddingsPath ?? \"/v1/embeddings\").replace(\n \"{model}\",\n encodeURIComponent(model),\n );\n\n const stats: EmbeddingRuntimeStats = {\n cacheHits: 0,\n cacheMisses: 0,\n httpCalls: 0,\n retries: 0,\n };\n\n return {\n id: cfg.id,\n model,\n dimension,\n stats: () => ({ ...stats }),\n embed: (texts) =>\n embedTexts({\n texts,\n cache,\n stats,\n model,\n dimension,\n apiKey,\n baseUrl,\n embeddingsPath,\n fetchImpl,\n providerId: cfg.id,\n dialect: cfg.dialect ?? {},\n }),\n };\n}\n\ninterface EmbedTextsInput {\n texts: ReadonlyArray<string>;\n cache: NonNullable<CreateAdapterOptions[\"cache\"]>;\n stats: EmbeddingRuntimeStats;\n model: string;\n dimension: number;\n apiKey: string;\n baseUrl: string;\n embeddingsPath: string;\n fetchImpl: typeof fetch;\n providerId: string;\n dialect: EmbeddingDialect;\n}\n\nasync function embedTexts(input: EmbedTextsInput): Promise<number[][]> {\n const { texts, cache, stats, model, dimension } = input;\n const results = new Array<number[] | undefined>(texts.length);\n const pending: Array<{ index: number; text: string; key: string }> = [];\n for (let i = 0; i < texts.length; i++) {\n classifyEntry({\n index: i,\n text: texts[i] ?? \"\",\n model,\n dimension,\n cache,\n stats,\n results,\n pending,\n });\n }\n await embedInBoundedBatches(input, pending, results);\n return results.map((v) => v ?? new Array(dimension).fill(0));\n}\n\ninterface ClassifyEntryArgs {\n index: number;\n text: string;\n model: string;\n dimension: number;\n cache: NonNullable<CreateAdapterOptions[\"cache\"]>;\n stats: EmbeddingRuntimeStats;\n results: Array<number[] | undefined>;\n pending: Array<{ index: number; text: string; key: string }>;\n}\n\nfunction classifyEntry(args: ClassifyEntryArgs): void {\n if (args.text.trim().length === 0) {\n args.results[args.index] = new Array(args.dimension).fill(0);\n return;\n }\n const key = hashKey(args.model, args.text);\n const cached = args.cache.get(key);\n if (cached !== undefined) {\n args.stats.cacheHits += 1;\n args.results[args.index] = cached;\n return;\n }\n args.stats.cacheMisses += 1;\n args.pending.push({ index: args.index, text: args.text, key });\n}\n\n// T4.3 — max concurrent embedding API requests per embed() call.\n// 3 is enough to saturate most embedding APIs without triggering\n// rate limits (each batch is already up to MAX_BATCH=100 texts).\nconst MAX_CONCURRENT_BATCHES = 3;\n\n/**\n * T4.3 — parallel embed batches (DR4 finding #3).\n *\n * Pre-T4.3 this was a serial `for` loop — each batch of 100 texts\n * awaited before the next HTTP request started. With 500 texts the\n * serial path took 5 × RTT; parallel takes max(RTT) × ceil(5/3).\n *\n * Uses bounded-concurrency `Promise.all` capped at 3 concurrent\n * HTTP requests to avoid overwhelming the embedding API.\n */\nasync function embedInBoundedBatches(\n input: EmbedTextsInput,\n pending: ReadonlyArray<{ index: number; text: string; key: string }>,\n results: Array<number[] | undefined>,\n): Promise<void> {\n const batches: Array<ReadonlyArray<{ index: number; text: string; key: string }>> = [];\n for (let offset = 0; offset < pending.length; offset += MAX_BATCH) {\n batches.push(pending.slice(offset, offset + MAX_BATCH));\n }\n // M0-2: bounded parallel via the shared ordered pool (was an inline\n // acquire/release clone — see plan m0-foundation-expose-primitives).\n await mapWithConcurrency(batches, MAX_CONCURRENT_BATCHES, (batch) =>\n processBatch(input, batch, results),\n );\n}\n\nasync function processBatch(\n input: EmbedTextsInput,\n batch: ReadonlyArray<{ index: number; text: string; key: string }>,\n results: Array<number[] | undefined>,\n): Promise<void> {\n const vectors = await embedBatch({\n apiKey: input.apiKey,\n baseUrl: input.baseUrl,\n embeddingsPath: input.embeddingsPath,\n model: input.model,\n inputs: batch.map((b) => b.text),\n fetchImpl: input.fetchImpl,\n stats: input.stats,\n providerId: input.providerId,\n dialect: input.dialect,\n });\n for (let j = 0; j < batch.length; j++) {\n const slot = batch[j];\n const vector = vectors[j];\n if (slot === undefined || vector === undefined) continue;\n results[slot.index] = vector;\n input.cache.set(slot.key, vector);\n }\n}\n\ninterface BatchOptions {\n apiKey: string;\n baseUrl: string;\n embeddingsPath: string;\n model: string;\n inputs: ReadonlyArray<string>;\n fetchImpl: typeof fetch;\n stats: EmbeddingRuntimeStats;\n providerId: string;\n dialect: EmbeddingDialect;\n}\n\nasync function embedBatch(opts: BatchOptions): Promise<number[][]> {\n // EC-2: embeddingsPath REPLACES the suffix; never concatenates.\n const url = `${opts.baseUrl.replace(/\\/$/, \"\")}${opts.embeddingsPath}`;\n let attempt = 0;\n while (true) {\n opts.stats.httpCalls += 1;\n const response = await postEmbedRequest(opts, url);\n if (response.ok)\n return await parseEmbedResponse(response, opts.providerId, opts.embeddingsPath, opts.dialect);\n if (isRetryable(response.status) && attempt < MAX_RETRIES) {\n attempt += 1;\n opts.stats.retries += 1;\n const retryAfterSec = parseRetryAfter(response.headers);\n await sleep(\n computeBackoffMs({\n attempt,\n baseMs: EMBED_BACKOFF_BASE_MS,\n capMs: EMBED_BACKOFF_CAP_MS,\n ...(retryAfterSec !== undefined ? { retryAfterMs: retryAfterSec * 1000 } : {}),\n }),\n );\n continue;\n }\n throw await mappedEmbedError(opts, response);\n }\n}\n\n/**\n * The terminal failure: read the body best-effort so the mapper sees the provider's error code, then\n * map it. Its own function because folding it back into the retry loop puts `embedBatch` at\n * cognitive complexity 11 against the repo max of 10 — and the sibling budget for those suppressions\n * is ratcheting DOWN, so adding one here to keep this loop whole would undo that.\n */\nasync function mappedEmbedError(opts: BatchOptions, response: Response): Promise<unknown> {\n const text = await response.text().catch(() => \"\");\n let body: unknown = text;\n try {\n body = JSON.parse(text);\n } catch {\n // not JSON — keep as string\n }\n return mapOpenAICompatibleError({\n providerId: opts.providerId,\n status: response.status,\n body,\n headers: response.headers,\n endpoint: opts.embeddingsPath,\n });\n}\n\nasync function postEmbedRequest(opts: BatchOptions, url: string): Promise<Response> {\n return opts.fetchImpl(url, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(opts.dialect.authHeaders?.(opts.apiKey) ?? { authorization: `Bearer ${opts.apiKey}` }),\n },\n body: JSON.stringify(\n opts.dialect.body?.(opts.model, opts.inputs) ?? { model: opts.model, input: opts.inputs },\n ),\n });\n}\n\nasync function parseEmbedResponse(\n response: Response,\n providerId: string,\n endpoint: string,\n dialect?: EmbeddingDialect,\n): Promise<number[][]> {\n const json = (await response.json()) as { data?: Array<{ embedding: number[] }> };\n // theokit#159 — a provider whose response is not OpenAI-shaped supplies its own reader.\n const viaDialect = dialect?.vectors?.(json);\n if (viaDialect !== undefined) return viaDialect;\n if (!Array.isArray(json.data)) {\n throw new NetworkError(`${providerId} ${endpoint} returned no data`, {\n code: \"embedding_invalid_response\",\n metadata: {\n provider: providerId,\n endpoint,\n code: \"invalid_request\",\n raw: json,\n },\n });\n }\n return json.data.map((d) => d.embedding);\n}\n\nfunction isRetryable(status: number): boolean {\n return status === 429 || (status >= 500 && status < 600);\n}\n\nfunction hashKey(model: string, text: string): string {\n return createHash(\"sha256\").update(`${model} ${text}`).digest(\"hex\");\n}\n\n/**\n * Not `sleepWithAbort`, and the reason is the parsimony ladder rather than an oversight.\n *\n * `EmbeddingRuntime.embed(texts)` takes no signal, and NONE of its seven callers — lance-index,\n * index-manager, vec-index, the two dreaming phases, scorers — holds one to pass. Adding the\n * parameter would add a seam nobody supplies, so cancellation is bounded instead of plumbed: at most\n * MAX_RETRIES sleeps of at most EMBED_BACKOFF_CAP_MS each. Give `embed()` a signal on the day a\n * caller has one, and this becomes `sleepWithAbort` in the same edit.\n */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"]}
|