@ashx-j/lunr-ai 0.2.23 → 0.2.25
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/dist/api/anthropic-claude-code-bridge.d.ts +6 -0
- package/dist/api/anthropic-claude-code-bridge.d.ts.map +1 -0
- package/dist/api/anthropic-claude-code-bridge.js +582 -0
- package/dist/api/anthropic-claude-code-bridge.js.map +1 -0
- package/dist/api/anthropic-messages.d.ts.map +1 -1
- package/dist/api/anthropic-messages.js +7 -19
- package/dist/api/anthropic-messages.js.map +1 -1
- package/dist/auth/anthropic-route.d.ts +7 -0
- package/dist/auth/anthropic-route.d.ts.map +1 -0
- package/dist/auth/anthropic-route.js +25 -0
- package/dist/auth/anthropic-route.js.map +1 -0
- package/dist/auth/oauth/anthropic.d.ts +1 -6
- package/dist/auth/oauth/anthropic.d.ts.map +1 -1
- package/dist/auth/oauth/anthropic.js +214 -277
- package/dist/auth/oauth/anthropic.js.map +1 -1
- package/dist/auth/resolve.d.ts.map +1 -1
- package/dist/auth/resolve.js +27 -0
- package/dist/auth/resolve.js.map +1 -1
- package/dist/auth/types.d.ts +15 -2
- package/dist/auth/types.d.ts.map +1 -1
- package/dist/auth/types.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/env-api-keys.d.ts.map +1 -1
- package/dist/env-api-keys.js +1 -2
- package/dist/env-api-keys.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +19 -3
- package/dist/models.js.map +1 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +32 -3
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/radius.d.ts.map +1 -1
- package/dist/providers/radius.js +5 -1
- package/dist/providers/radius.js.map +1 -1
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +15 -2
- package/vendor/hermes-claude-subscription-directsdk/ATTRIBUTION.md +9 -0
- package/vendor/hermes-claude-subscription-directsdk/LICENSE +21 -0
- package/vendor/hermes-claude-subscription-directsdk/README.md +179 -0
- package/vendor/hermes-claude-subscription-directsdk/admission.py +201 -0
- package/vendor/hermes-claude-subscription-directsdk/agent/reasoning_effort.py +17 -0
- package/vendor/hermes-claude-subscription-directsdk/directsdk.py +629 -0
- package/vendor/hermes-claude-subscription-directsdk/directsdk_setup.py +116 -0
- package/vendor/hermes-claude-subscription-directsdk/inert_mcp.py +24 -0
- package/vendor/hermes-claude-subscription-directsdk/lunr_bridge.py +89 -0
- package/vendor/hermes-claude-subscription-directsdk/lunr_setup_bridge.py +49 -0
- package/vendor/hermes-claude-subscription-directsdk/manifest.json +32 -0
- package/vendor/hermes-claude-subscription-directsdk/model_catalog.py +44 -0
- package/vendor/hermes-claude-subscription-directsdk/patches/0001-lunr-lifecycle-hooks.patch +14 -0
- package/vendor/hermes-claude-subscription-directsdk/tools/schema_sanitizer.py +23 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Api, Context, Model, SimpleStreamOptions, StreamOptions } from "../types.ts";
|
|
2
|
+
import { AssistantMessageEventStream } from "../utils/event-stream.ts";
|
|
3
|
+
export declare function claudeCodeEnvironment(configDir?: string, strict?: boolean): Record<string, string>;
|
|
4
|
+
export declare function stopNative(pid: number): Promise<void>;
|
|
5
|
+
export declare function streamClaudeCode(model: Model<Api>, context: Context, options?: StreamOptions | SimpleStreamOptions): AssistantMessageEventStream;
|
|
6
|
+
//# sourceMappingURL=anthropic-claude-code-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-claude-code-bridge.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-claude-code-bridge.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACX,GAAG,EAEH,OAAO,EACP,KAAK,EACL,mBAAmB,EACnB,aAAa,EAGb,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAwCvE,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,UAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoB/F;AAmJD,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3D;AAgGD,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,aAAa,GAAG,mBAAwB,GAC/C,2BAA2B,CA2R7B","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ExternalClaudeCodeCredential } from \"../auth/types.ts\";\nimport { calculateCost } from \"../models.ts\";\nimport type {\n\tApi,\n\tAssistantMessage,\n\tContext,\n\tModel,\n\tSimpleStreamOptions,\n\tStreamOptions,\n\tToolCall,\n\tUsage,\n} from \"../types.ts\";\nimport { AssistantMessageEventStream } from \"../utils/event-stream.ts\";\n\nconst WORKER = fileURLToPath(\n\tnew URL(\"../../vendor/hermes-claude-subscription-directsdk/lunr_bridge.py\", import.meta.url),\n);\nconst MAX_RECORD = 64 * 1024 * 1024;\nconst CONFLICTS = [\n\t\"ANTHROPIC_API_KEY\",\n\t\"ANTHROPIC_AUTH_TOKEN\",\n\t\"ANTHROPIC_OAUTH_TOKEN\",\n\t\"ANTHROPIC_BASE_URL\",\n\t\"ANTHROPIC_FOUNDRY_API_KEY\",\n\t\"CLAUDE_CODE_USE_BEDROCK\",\n\t\"CLAUDE_CODE_USE_VERTEX\",\n\t\"CLAUDE_CODE_USE_FOUNDRY\",\n\t\"CLAUDE_CODE_EXTRA_BODY\",\n] as const;\nconst ENV_ALLOWLIST = [\n\t\"PATH\",\n\t\"HOME\",\n\t\"USERPROFILE\",\n\t\"APPDATA\",\n\t\"LOCALAPPDATA\",\n\t\"PROGRAMDATA\",\n\t\"SYSTEMROOT\",\n\t\"SYSTEMDRIVE\",\n\t\"COMSPEC\",\n\t\"PATHEXT\",\n\t\"TEMP\",\n\t\"TMP\",\n\t\"LANG\",\n\t\"LC_ALL\",\n\t\"HTTP_PROXY\",\n\t\"HTTPS_PROXY\",\n\t\"NO_PROXY\",\n\t\"http_proxy\",\n\t\"https_proxy\",\n\t\"no_proxy\",\n] as const;\n\nexport function claudeCodeEnvironment(configDir?: string, strict = true): Record<string, string> {\n\tconst conflicting = CONFLICTS.filter((name) => {\n\t\tconst value = process.env[name];\n\t\treturn (\n\t\t\tvalue &&\n\t\t\t((name !== \"CLAUDE_CODE_USE_BEDROCK\" &&\n\t\t\t\tname !== \"CLAUDE_CODE_USE_VERTEX\" &&\n\t\t\t\tname !== \"CLAUDE_CODE_USE_FOUNDRY\") ||\n\t\t\t\t![\"0\", \"false\", \"no\", \"off\"].includes(value.toLowerCase()))\n\t\t);\n\t});\n\tif (strict && conflicting.length)\n\t\tthrow new Error(`Claude Code subscription refuses conflicting environment variables: ${conflicting.join(\", \")}`);\n\tif (process.env.NODE_TLS_REJECT_UNAUTHORIZED === \"0\")\n\t\tthrow new Error(\"Claude Code subscription requires TLS certificate verification\");\n\tconst env = Object.fromEntries(\n\t\tENV_ALLOWLIST.flatMap((name) => (process.env[name] ? [[name, process.env[name]]] : [])),\n\t);\n\tif (configDir) env.CLAUDE_SUBSCRIPTION_DIRECTSDK_CONFIG_DIR = configDir;\n\treturn env;\n}\n\nfunction contentHash(content: AssistantMessage[\"content\"]): string {\n\treturn createHash(\"sha256\").update(JSON.stringify(content)).digest(\"hex\");\n}\n\nfunction history(\n\tcontext: Context,\n\tmodel: Model<Api>,\n\tconnection: ExternalClaudeCodeCredential,\n): { messages: unknown[]; tools: unknown[] } {\n\tconst messages: Record<string, unknown>[] = context.systemPrompt\n\t\t? [{ role: \"system\", content: context.systemPrompt }]\n\t\t: [];\n\tfor (const message of context.messages) {\n\t\tif (message.role === \"user\") {\n\t\t\tmessages.push({\n\t\t\t\trole: \"user\",\n\t\t\t\tcontent:\n\t\t\t\t\ttypeof message.content === \"string\"\n\t\t\t\t\t\t? message.content\n\t\t\t\t\t\t: message.content.map((block) =>\n\t\t\t\t\t\t\t\tblock.type === \"text\"\n\t\t\t\t\t\t\t\t\t? { type: \"text\", text: block.text }\n\t\t\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\t\t\ttype: \"image_url\",\n\t\t\t\t\t\t\t\t\t\t\timage_url: { url: `data:${block.mimeType};base64,${block.data}` },\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t),\n\t\t\t});\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst text = message.content\n\t\t\t\t.filter((block) => block.type === \"text\")\n\t\t\t\t.map((block) => block.text)\n\t\t\t\t.join(\"\");\n\t\t\tconst calls = message.content\n\t\t\t\t.filter((block): block is ToolCall => block.type === \"toolCall\")\n\t\t\t\t.map((block) => ({\n\t\t\t\t\tid: block.id,\n\t\t\t\t\ttype: \"function\",\n\t\t\t\t\tfunction: { name: block.name, arguments: JSON.stringify(block.arguments) },\n\t\t\t\t}));\n\t\t\tconst projection = {\n\t\t\t\tcontent: text.trim(),\n\t\t\t\ttool_calls: calls.map((call) => ({\n\t\t\t\t\tid: call.id,\n\t\t\t\t\tname: call.function.name,\n\t\t\t\t\tinput: JSON.parse(call.function.arguments),\n\t\t\t\t})),\n\t\t\t};\n\t\t\tconst carrier =\n\t\t\t\tmessage.provider === \"anthropic\" &&\n\t\t\t\tmessage.model === model.id &&\n\t\t\t\tmessage.claudeCodeCarrier?.version === 1 &&\n\t\t\t\tconnection.accountFingerprint &&\n\t\t\t\tmessage.claudeCodeCarrier.accountFingerprint === connection.accountFingerprint &&\n\t\t\t\tmessage.claudeCodeCarrier.contentHash === contentHash(message.content) &&\n\t\t\t\tJSON.stringify(message.claudeCodeCarrier.projection) === JSON.stringify(projection)\n\t\t\t\t\t? [{ type: \"claude-subscription-directsdk-experimental.native_assistant\", ...message.claudeCodeCarrier }]\n\t\t\t\t\t: [];\n\t\t\tmessages.push({ role: \"assistant\", content: text, tool_calls: calls, reasoning_details: carrier });\n\t\t} else {\n\t\t\tif (message.content.some((block) => block.type !== \"text\"))\n\t\t\t\tthrow new Error(\"Claude Code subscription supports text tool results only\");\n\t\t\tmessages.push({\n\t\t\t\trole: \"tool\",\n\t\t\t\ttool_call_id: message.toolCallId,\n\t\t\t\tcontent: message.content.map((block) => (block.type === \"text\" ? block.text : \"\")).join(\"\"),\n\t\t\t\tis_error: message.isError,\n\t\t\t});\n\t\t}\n\t}\n\tconst tools = (context.tools ?? []).map((tool) => ({\n\t\ttype: \"function\",\n\t\tfunction: {\n\t\t\tname: tool.name,\n\t\t\tdescription: tool.description,\n\t\t\tparameters: tool.parameters,\n\t\t},\n\t}));\n\treturn { messages, tools };\n}\n\nfunction validateOptions(model: Model<Api>, options: StreamOptions | SimpleStreamOptions): void {\n\tif (!options.externalClaudeCode)\n\t\tthrow new Error(\"Claude Code subscription setup is required. Run /login anthropic.\");\n\tif (\n\t\toptions.apiKey ||\n\t\tObject.keys(options.headers ?? {}).length ||\n\t\toptions.onPayload ||\n\t\toptions.onResponse ||\n\t\toptions.env ||\n\t\toptions.metadata ||\n\t\t(options.transport && options.transport !== \"auto\") ||\n\t\toptions.maxRetries ||\n\t\t(options.maxRetryDelayMs !== undefined && options.maxRetryDelayMs !== 60_000) ||\n\t\toptions.websocketConnectTimeoutMs ||\n\t\tmodel.baseUrl !== \"https://api.anthropic.com\"\n\t) {\n\t\tthrow new Error(\"Claude Code subscription does not support API keys, alternate endpoints, or request mutations\");\n\t}\n\tconst anthro = options as StreamOptions & {\n\t\ttoolChoice?: unknown;\n\t\tclient?: unknown;\n\t\textraBody?: unknown;\n\t\tthinkingBudgetTokens?: unknown;\n\t\tstopSequences?: unknown;\n\t\tthinkingEnabled?: boolean;\n\t\teffort?: string;\n\t\tthinkingDisplay?: unknown;\n\t\tinterleavedThinking?: unknown;\n\t};\n\tif (\n\t\t(anthro.toolChoice && anthro.toolChoice !== \"auto\") ||\n\t\tanthro.client ||\n\t\tanthro.extraBody ||\n\t\tanthro.thinkingBudgetTokens ||\n\t\tanthro.stopSequences ||\n\t\tanthro.thinkingDisplay !== undefined ||\n\t\tanthro.interleavedThinking !== undefined\n\t) {\n\t\tthrow new Error(\"Claude Code subscription does not support forced tools or native request overrides\");\n\t}\n\tif (options.temperature !== undefined)\n\t\tthrow new Error(\"Claude Code subscription does not support sampling overrides\");\n}\n\ntype WorkerEvent = { v: 1; requestId: string; type: string; text?: unknown; pid?: unknown; response?: unknown };\n\nfunction parseWorkerRecord(line: string, requestId: string): WorkerEvent {\n\tif (Buffer.byteLength(line) > MAX_RECORD) throw new Error(\"Claude Code bridge record exceeds size limit\");\n\tconst value: unknown = JSON.parse(line);\n\tif (\n\t\t!value ||\n\t\ttypeof value !== \"object\" ||\n\t\t!(\"v\" in value) ||\n\t\tvalue.v !== 1 ||\n\t\t!(\"requestId\" in value) ||\n\t\tvalue.requestId !== requestId ||\n\t\t!(\"type\" in value) ||\n\t\ttypeof value.type !== \"string\"\n\t) {\n\t\tthrow new Error(\"Invalid Claude Code bridge record\");\n\t}\n\treturn value as WorkerEvent;\n}\n\nexport async function stopNative(pid: number): Promise<void> {\n\tif (!Number.isSafeInteger(pid) || pid < 1) return;\n\tif (process.platform === \"win32\") {\n\t\tawait new Promise<void>((resolve) => {\n\t\t\tconst killer = spawn(\"taskkill\", [\"/F\", \"/T\", \"/PID\", String(pid)], { stdio: \"ignore\", windowsHide: true });\n\t\t\tconst timer = setTimeout(() => {\n\t\t\t\tkiller.kill();\n\t\t\t\tresolve();\n\t\t\t}, 3000);\n\t\t\tkiller.once(\"error\", () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t\tkiller.once(\"close\", () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t} else {\n\t\ttry {\n\t\t\tprocess.kill(-pid, \"SIGKILL\");\n\t\t} catch {\n\t\t\t/* Already exited. */\n\t\t}\n\t}\n}\n\nfunction validatedCompletion(\n\tvalue: unknown,\n\tmodel: Model<Api>,\n\ttext: string,\n\tthinking: string,\n): {\n\tcontent: AssistantMessage[\"content\"];\n\tcarrier: AssistantMessage[\"claudeCodeCarrier\"];\n\tusage: Usage;\n\tresponseId: string;\n\treason: \"stop\" | \"length\" | \"toolUse\";\n} {\n\tif (!value || typeof value !== \"object\" || !(\"choices\" in value) || !Array.isArray(value.choices))\n\t\tthrow new Error(\"Invalid native completion\");\n\tconst response = value as Record<string, unknown> & {\n\t\tchoices: { finish_reason: unknown; message: Record<string, unknown> }[];\n\t\tusage: Record<string, unknown>;\n\t};\n\tconst choice = response.choices[0];\n\tif (\n\t\t!choice ||\n\t\t![\"stop\", \"length\", \"tool_calls\"].includes(String(choice.finish_reason)) ||\n\t\t(typeof choice.message?.content !== \"string\" && choice.message?.content !== null) ||\n\t\t(choice.message.content ?? \"\") !== text ||\n\t\t(choice.message.reasoning_content ?? \"\") !== thinking\n\t)\n\t\tthrow new Error(\"Native completion differs from streamed content\");\n\tconst rawCalls = choice.message.tool_calls ?? [];\n\tif (!Array.isArray(rawCalls)) throw new Error(\"Invalid native tool calls\");\n\tconst calls: ToolCall[] = rawCalls.map((call) => {\n\t\tif (\n\t\t\t!call ||\n\t\t\ttypeof call !== \"object\" ||\n\t\t\ttypeof call.id !== \"string\" ||\n\t\t\ttypeof call.function?.name !== \"string\" ||\n\t\t\ttypeof call.function?.arguments !== \"string\"\n\t\t)\n\t\t\tthrow new Error(\"Invalid native tool call\");\n\t\tconst args: unknown = JSON.parse(call.function.arguments);\n\t\tif (!args || typeof args !== \"object\" || Array.isArray(args)) throw new Error(\"Invalid native tool arguments\");\n\t\treturn { type: \"toolCall\", id: call.id, name: call.function.name, arguments: args as Record<string, unknown> };\n\t});\n\tconst native = response.usage;\n\tif (\n\t\t!native ||\n\t\ttypeof native.native_admission !== \"object\" ||\n\t\tnative.native_admission === null ||\n\t\t(native.native_admission as { upstream_requests?: unknown }).upstream_requests !== 1\n\t)\n\t\tthrow new Error(\"Native one-request admission not verified\");\n\tif (\n\t\t!native ||\n\t\t!Number.isFinite(native.prompt_tokens) ||\n\t\t!Number.isFinite(native.completion_tokens) ||\n\t\t!Number.isFinite(native.cache_creation_input_tokens) ||\n\t\t!(\"prompt_tokens_details\" in native)\n\t)\n\t\tthrow new Error(\"Missing native usage\");\n\tconst cached = (native.prompt_tokens_details as { cached_tokens?: unknown }).cached_tokens;\n\tif (!Number.isFinite(cached)) throw new Error(\"Missing native cache usage\");\n\tconst usage: Usage = {\n\t\tinput: Number(native.prompt_tokens) - Number(cached) - Number(native.cache_creation_input_tokens),\n\t\toutput: Number(native.completion_tokens),\n\t\tcacheRead: Number(cached),\n\t\tcacheWrite: Number(native.cache_creation_input_tokens),\n\t\ttotalTokens: Number(native.total_tokens),\n\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t};\n\tif (usage.input < 0 || usage.output < 0) throw new Error(\"Invalid native usage\");\n\tcalculateCost(model, usage);\n\tconst details = choice.message.reasoning_details;\n\tconst carrier = Array.isArray(details)\n\t\t? details.find((entry) => entry?.type === \"claude-subscription-directsdk-experimental.native_assistant\")\n\t\t: undefined;\n\tconst projection = {\n\t\tcontent: text.trim(),\n\t\ttool_calls: calls.map((call) => ({ id: call.id, name: call.name, input: call.arguments })),\n\t};\n\tif (\n\t\t!carrier ||\n\t\tcarrier.version !== 1 ||\n\t\t!Array.isArray(carrier.messages) ||\n\t\tJSON.stringify(carrier.projection) !== JSON.stringify(projection)\n\t)\n\t\tthrow new Error(\"Invalid native replay carrier\");\n\treturn {\n\t\tcontent: [...(text ? [{ type: \"text\" as const, text }] : []), ...calls],\n\t\tcarrier: { version: 1, messages: carrier.messages, projection },\n\t\tusage,\n\t\tresponseId: String(response.id ?? \"\"),\n\t\treason: choice.finish_reason === \"tool_calls\" ? \"toolUse\" : choice.finish_reason === \"length\" ? \"length\" : \"stop\",\n\t};\n}\n\nexport function streamClaudeCode(\n\tmodel: Model<Api>,\n\tcontext: Context,\n\toptions: StreamOptions | SimpleStreamOptions = {},\n): AssistantMessageEventStream {\n\tconst stream = new AssistantMessageEventStream();\n\tconst output: AssistantMessage = {\n\t\trole: \"assistant\",\n\t\tapi: model.api,\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tcontent: [],\n\t\tusage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: Date.now(),\n\t};\n\tvoid (async () => {\n\t\tlet child: ChildProcessWithoutNullStreams | undefined;\n\t\tlet nativePid: number | undefined;\n\t\tlet nativeCleanup: Promise<void> | undefined;\n\t\tconst stopNativeOnce = () => {\n\t\t\tif (nativePid === undefined) return Promise.resolve();\n\t\t\tnativeCleanup ??= stopNative(nativePid);\n\t\t\treturn nativeCleanup;\n\t\t};\n\t\tlet timer: NodeJS.Timeout | undefined;\n\t\tlet abortTimer: NodeJS.Timeout | undefined;\n\t\tlet started = false;\n\t\tlet complete = false;\n\t\tlet buffer = \"\";\n\t\tconst decoder = new TextDecoder(\"utf-8\", { fatal: true });\n\t\tlet text = \"\";\n\t\tlet thinking = \"\";\n\t\tlet activeIndex = -1;\n\t\tlet activeType: \"text\" | \"thinking\" | undefined;\n\t\ttry {\n\t\t\tvalidateOptions(model, options);\n\t\t\tif (options.signal?.aborted) throw new Error(\"Claude Code request cancelled\");\n\t\t\tconst connection = options.externalClaudeCode!;\n\t\t\tconst env = claudeCodeEnvironment(connection.configDir);\n\t\t\tconst input = history(context, model, connection);\n\t\t\tconst anthropicOptions = options as StreamOptions & { thinkingEnabled?: boolean; effort?: string };\n\t\t\tconst reasoning =\n\t\t\t\t\"reasoning\" in options && options.reasoning\n\t\t\t\t\t? { enabled: true, effort: options.reasoning }\n\t\t\t\t\t: anthropicOptions.thinkingEnabled !== undefined || anthropicOptions.effort\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tenabled: anthropicOptions.thinkingEnabled ?? true,\n\t\t\t\t\t\t\t\t...(anthropicOptions.effort ? { effort: anthropicOptions.effort } : {}),\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: undefined;\n\t\t\tconst requestId = randomUUID();\n\t\t\tchild = spawn(connection.python, [\"-s\", \"-B\", \"-u\", WORKER], {\n\t\t\t\tstdio: [\"pipe\", \"pipe\", \"pipe\"],\n\t\t\t\twindowsHide: true,\n\t\t\t\tenv,\n\t\t\t});\n\t\t\tconst worker = child;\n\t\t\tworker.stderr.resume();\n\t\t\tworker.stdin.on(\"error\", () => {});\n\t\t\tconst idleMs = Math.min(300_000, Math.max(1000, options.timeoutMs ?? 180_000));\n\t\t\tconst record = {\n\t\t\t\tv: 1,\n\t\t\t\trequestId,\n\t\t\t\ttype: \"start\",\n\t\t\t\tcommand: connection.command,\n\t\t\t\tenv,\n\t\t\t\tmodel: model.id,\n\t\t\t\t...input,\n\t\t\t\textraBody: {\n\t\t\t\t\t...(options.maxTokens ? { max_tokens: options.maxTokens } : {}),\n\t\t\t\t\t...(reasoning ? { reasoning } : {}),\n\t\t\t\t},\n\t\t\t\ttimeout: Math.round(idleMs / 1000),\n\t\t\t};\n\t\t\tconst payload = JSON.stringify(record);\n\t\t\tif (Buffer.byteLength(payload) > MAX_RECORD) throw new Error(\"Claude Code request exceeds size limit\");\n\t\t\tworker.stdin.write(`${payload}\\n`);\n\t\t\tlet cancelSent = false;\n\t\t\tlet timedOut = false;\n\t\t\tconst abort = () => {\n\t\t\t\tif (cancelSent) return;\n\t\t\t\tcancelSent = true;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tworker.stdin.write(`${JSON.stringify({ v: 1, requestId, type: \"cancel\" })}\\n`);\n\t\t\t\tabortTimer = setTimeout(() => {\n\t\t\t\t\tvoid stopNativeOnce().then(() => worker.kill());\n\t\t\t\t}, 2500);\n\t\t\t};\n\t\t\tconst resetDeadline = () => {\n\t\t\t\tif (cancelSent) return;\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\ttimer = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tabort();\n\t\t\t\t}, idleMs);\n\t\t\t};\n\t\t\ttimer = setTimeout(\n\t\t\t\t() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tabort();\n\t\t\t\t},\n\t\t\t\tMath.min(30_000, idleMs),\n\t\t\t);\n\t\t\toptions.signal?.addEventListener(\"abort\", abort, { once: true });\n\t\t\tif (options.signal?.aborted) abort();\n\t\t\ttry {\n\t\t\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\t\t\tworker.on(\"error\", reject);\n\t\t\t\t\tworker.stdout.on(\"data\", (data: Buffer) => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tbuffer += decoder.decode(data, { stream: true });\n\t\t\t\t\t\t\tif (Buffer.byteLength(buffer) > MAX_RECORD)\n\t\t\t\t\t\t\t\tthrow new Error(\"Claude Code bridge record exceeds size limit\");\n\t\t\t\t\t\t\tfor (let newline = buffer.indexOf(\"\\n\"); newline >= 0; newline = buffer.indexOf(\"\\n\")) {\n\t\t\t\t\t\t\t\tconst event = parseWorkerRecord(buffer.slice(0, newline), requestId);\n\t\t\t\t\t\t\t\tbuffer = buffer.slice(newline + 1);\n\t\t\t\t\t\t\t\tif (event.type === \"ready\" || event.type === \"replay_progress\") resetDeadline();\n\t\t\t\t\t\t\t\telse if (\n\t\t\t\t\t\t\t\t\tevent.type === \"native_started\" &&\n\t\t\t\t\t\t\t\t\tNumber.isSafeInteger(event.pid) &&\n\t\t\t\t\t\t\t\t\tNumber(event.pid) > 0\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tnativePid = Number(event.pid);\n\t\t\t\t\t\t\t\t\tresetDeadline();\n\t\t\t\t\t\t\t\t} else if (\n\t\t\t\t\t\t\t\t\tcancelSent &&\n\t\t\t\t\t\t\t\t\t[\"start\", \"text_delta\", \"thinking_delta\", \"complete\"].includes(event.type)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t\telse if (event.type === \"start\") {\n\t\t\t\t\t\t\t\t\tif (started) throw new Error(\"Duplicate Claude Code stream start\");\n\t\t\t\t\t\t\t\t\tstarted = true;\n\t\t\t\t\t\t\t\t\tstream.push({ type: \"start\", partial: output });\n\t\t\t\t\t\t\t\t} else if (event.type === \"text_delta\" || event.type === \"thinking_delta\") {\n\t\t\t\t\t\t\t\t\tif (!started || complete || typeof event.text !== \"string\")\n\t\t\t\t\t\t\t\t\t\tthrow new Error(\"Invalid Claude Code stream delta\");\n\t\t\t\t\t\t\t\t\tresetDeadline();\n\t\t\t\t\t\t\t\t\tconst kind = event.type === \"text_delta\" ? \"text\" : \"thinking\";\n\t\t\t\t\t\t\t\t\tif (activeType !== kind) {\n\t\t\t\t\t\t\t\t\t\tif (activeType === \"text\")\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { text: string }).text,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tif (activeType === \"thinking\")\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { thinking: string }).thinking,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\tactiveIndex = output.content.length;\n\t\t\t\t\t\t\t\t\t\tactiveType = kind;\n\t\t\t\t\t\t\t\t\t\tif (kind === \"text\") {\n\t\t\t\t\t\t\t\t\t\t\toutput.content.push({ type: \"text\", text: \"\" });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"text_start\", contentIndex: activeIndex, partial: output });\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\toutput.content.push({ type: \"thinking\", thinking: \"\" });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"thinking_start\", contentIndex: activeIndex, partial: output });\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tconst block = output.content[activeIndex];\n\t\t\t\t\t\t\t\t\tif (kind === \"text\" && block?.type === \"text\") {\n\t\t\t\t\t\t\t\t\t\ttext += event.text;\n\t\t\t\t\t\t\t\t\t\tblock.text += event.text;\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text_delta\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (kind === \"thinking\" && block?.type === \"thinking\") {\n\t\t\t\t\t\t\t\t\t\tthinking += event.text;\n\t\t\t\t\t\t\t\t\t\tblock.thinking += event.text;\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_delta\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tdelta: event.text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (event.type === \"complete\") {\n\t\t\t\t\t\t\t\t\tif (!started || complete) throw new Error(\"Invalid Claude Code completion order\");\n\t\t\t\t\t\t\t\t\tconst result = validatedCompletion(event.response, model, text, thinking);\n\t\t\t\t\t\t\t\t\tcomplete = true;\n\t\t\t\t\t\t\t\t\tif (activeType === \"text\")\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"text_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { text: string }).text,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\tif (activeType === \"thinking\")\n\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\ttype: \"thinking_end\",\n\t\t\t\t\t\t\t\t\t\t\tcontentIndex: activeIndex,\n\t\t\t\t\t\t\t\t\t\t\tcontent: (output.content[activeIndex] as { thinking: string }).thinking,\n\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\toutput.content = [\n\t\t\t\t\t\t\t\t\t\t...output.content.filter((block) => block.type !== \"toolCall\"),\n\t\t\t\t\t\t\t\t\t\t...result.content.filter((block) => block.type === \"toolCall\"),\n\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t\toutput.claudeCodeCarrier = result.carrier && {\n\t\t\t\t\t\t\t\t\t\t...result.carrier,\n\t\t\t\t\t\t\t\t\t\tcontentHash: contentHash(output.content),\n\t\t\t\t\t\t\t\t\t\taccountFingerprint: connection.accountFingerprint,\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\toutput.responseId = result.responseId;\n\t\t\t\t\t\t\t\t\toutput.usage = result.usage;\n\t\t\t\t\t\t\t\t\toutput.stopReason = result.reason;\n\t\t\t\t\t\t\t\t\tfor (let index = 0; index < output.content.length; index++) {\n\t\t\t\t\t\t\t\t\t\tconst block = output.content[index];\n\t\t\t\t\t\t\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\t\t\t\t\t\t\tstream.push({ type: \"toolcall_start\", contentIndex: index, partial: output });\n\t\t\t\t\t\t\t\t\t\t\tstream.push({\n\t\t\t\t\t\t\t\t\t\t\t\ttype: \"toolcall_end\",\n\t\t\t\t\t\t\t\t\t\t\t\tcontentIndex: index,\n\t\t\t\t\t\t\t\t\t\t\t\ttoolCall: block,\n\t\t\t\t\t\t\t\t\t\t\t\tpartial: output,\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (event.type === \"cancelled\" && cancelSent) {\n\t\t\t\t\t\t\t\t} else if (event.type === \"error\" || event.type === \"cancelled\")\n\t\t\t\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t\t\t\t\"Claude Code request failed. Check subscription setup and native CLI compatibility.\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\telse throw new Error(\"Unknown Claude Code bridge event\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tworker.on(\"close\", () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tdecoder.decode();\n\t\t\t\t\t\t\tif (complete || cancelSent) resolve();\n\t\t\t\t\t\t\telse reject(new Error(\"Claude Code bridge exited without a validated completion\"));\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t} finally {\n\t\t\t\toptions.signal?.removeEventListener(\"abort\", abort);\n\t\t\t}\n\t\t\tif (options.signal?.aborted || timedOut) throw new Error(\"Claude Code request cancelled or timed out\");\n\t\t\tstream.push({ type: \"done\", reason: output.stopReason as \"stop\" | \"length\" | \"toolUse\", message: output });\n\t\t} catch (error) {\n\t\t\toutput.content = [];\n\t\t\toutput.claudeCodeCarrier = undefined;\n\t\t\toutput.stopReason = options.signal?.aborted ? \"aborted\" : \"error\";\n\t\t\tconst detail =\n\t\t\t\terror instanceof Error &&\n\t\t\t\t(error.message.startsWith(\"Claude Code subscription setup is required\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription does not support\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription refuses conflicting\") ||\n\t\t\t\t\terror.message.startsWith(\"Claude Code subscription requires TLS\"))\n\t\t\t\t\t? error.message\n\t\t\t\t\t: undefined;\n\t\t\toutput.errorMessage = options.signal?.aborted\n\t\t\t\t? \"Claude Code request cancelled\"\n\t\t\t\t: (detail ??\n\t\t\t\t\t\"Claude Code request failed. Run /login anthropic to check setup and Claude Code version 2.1.263.\");\n\t\t\tstream.push({ type: \"error\", reason: output.stopReason, error: output });\n\t\t} finally {\n\t\t\tif (timer) clearTimeout(timer);\n\t\t\tif (abortTimer) clearTimeout(abortTimer);\n\t\t\tif (child) {\n\t\t\t\tif (!complete || child.exitCode === null) await stopNativeOnce();\n\t\t\t\tif (child.exitCode === null) child.kill();\n\t\t\t}\n\t\t}\n\t})();\n\treturn stream;\n}\n"]}
|
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { calculateCost } from "../models.js";
|
|
5
|
+
import { AssistantMessageEventStream } from "../utils/event-stream.js";
|
|
6
|
+
const WORKER = fileURLToPath(new URL("../../vendor/hermes-claude-subscription-directsdk/lunr_bridge.py", import.meta.url));
|
|
7
|
+
const MAX_RECORD = 64 * 1024 * 1024;
|
|
8
|
+
const CONFLICTS = [
|
|
9
|
+
"ANTHROPIC_API_KEY",
|
|
10
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
11
|
+
"ANTHROPIC_OAUTH_TOKEN",
|
|
12
|
+
"ANTHROPIC_BASE_URL",
|
|
13
|
+
"ANTHROPIC_FOUNDRY_API_KEY",
|
|
14
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
15
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
16
|
+
"CLAUDE_CODE_USE_FOUNDRY",
|
|
17
|
+
"CLAUDE_CODE_EXTRA_BODY",
|
|
18
|
+
];
|
|
19
|
+
const ENV_ALLOWLIST = [
|
|
20
|
+
"PATH",
|
|
21
|
+
"HOME",
|
|
22
|
+
"USERPROFILE",
|
|
23
|
+
"APPDATA",
|
|
24
|
+
"LOCALAPPDATA",
|
|
25
|
+
"PROGRAMDATA",
|
|
26
|
+
"SYSTEMROOT",
|
|
27
|
+
"SYSTEMDRIVE",
|
|
28
|
+
"COMSPEC",
|
|
29
|
+
"PATHEXT",
|
|
30
|
+
"TEMP",
|
|
31
|
+
"TMP",
|
|
32
|
+
"LANG",
|
|
33
|
+
"LC_ALL",
|
|
34
|
+
"HTTP_PROXY",
|
|
35
|
+
"HTTPS_PROXY",
|
|
36
|
+
"NO_PROXY",
|
|
37
|
+
"http_proxy",
|
|
38
|
+
"https_proxy",
|
|
39
|
+
"no_proxy",
|
|
40
|
+
];
|
|
41
|
+
export function claudeCodeEnvironment(configDir, strict = true) {
|
|
42
|
+
const conflicting = CONFLICTS.filter((name) => {
|
|
43
|
+
const value = process.env[name];
|
|
44
|
+
return (value &&
|
|
45
|
+
((name !== "CLAUDE_CODE_USE_BEDROCK" &&
|
|
46
|
+
name !== "CLAUDE_CODE_USE_VERTEX" &&
|
|
47
|
+
name !== "CLAUDE_CODE_USE_FOUNDRY") ||
|
|
48
|
+
!["0", "false", "no", "off"].includes(value.toLowerCase())));
|
|
49
|
+
});
|
|
50
|
+
if (strict && conflicting.length)
|
|
51
|
+
throw new Error(`Claude Code subscription refuses conflicting environment variables: ${conflicting.join(", ")}`);
|
|
52
|
+
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED === "0")
|
|
53
|
+
throw new Error("Claude Code subscription requires TLS certificate verification");
|
|
54
|
+
const env = Object.fromEntries(ENV_ALLOWLIST.flatMap((name) => (process.env[name] ? [[name, process.env[name]]] : [])));
|
|
55
|
+
if (configDir)
|
|
56
|
+
env.CLAUDE_SUBSCRIPTION_DIRECTSDK_CONFIG_DIR = configDir;
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
function contentHash(content) {
|
|
60
|
+
return createHash("sha256").update(JSON.stringify(content)).digest("hex");
|
|
61
|
+
}
|
|
62
|
+
function history(context, model, connection) {
|
|
63
|
+
const messages = context.systemPrompt
|
|
64
|
+
? [{ role: "system", content: context.systemPrompt }]
|
|
65
|
+
: [];
|
|
66
|
+
for (const message of context.messages) {
|
|
67
|
+
if (message.role === "user") {
|
|
68
|
+
messages.push({
|
|
69
|
+
role: "user",
|
|
70
|
+
content: typeof message.content === "string"
|
|
71
|
+
? message.content
|
|
72
|
+
: message.content.map((block) => block.type === "text"
|
|
73
|
+
? { type: "text", text: block.text }
|
|
74
|
+
: {
|
|
75
|
+
type: "image_url",
|
|
76
|
+
image_url: { url: `data:${block.mimeType};base64,${block.data}` },
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else if (message.role === "assistant") {
|
|
81
|
+
const text = message.content
|
|
82
|
+
.filter((block) => block.type === "text")
|
|
83
|
+
.map((block) => block.text)
|
|
84
|
+
.join("");
|
|
85
|
+
const calls = message.content
|
|
86
|
+
.filter((block) => block.type === "toolCall")
|
|
87
|
+
.map((block) => ({
|
|
88
|
+
id: block.id,
|
|
89
|
+
type: "function",
|
|
90
|
+
function: { name: block.name, arguments: JSON.stringify(block.arguments) },
|
|
91
|
+
}));
|
|
92
|
+
const projection = {
|
|
93
|
+
content: text.trim(),
|
|
94
|
+
tool_calls: calls.map((call) => ({
|
|
95
|
+
id: call.id,
|
|
96
|
+
name: call.function.name,
|
|
97
|
+
input: JSON.parse(call.function.arguments),
|
|
98
|
+
})),
|
|
99
|
+
};
|
|
100
|
+
const carrier = message.provider === "anthropic" &&
|
|
101
|
+
message.model === model.id &&
|
|
102
|
+
message.claudeCodeCarrier?.version === 1 &&
|
|
103
|
+
connection.accountFingerprint &&
|
|
104
|
+
message.claudeCodeCarrier.accountFingerprint === connection.accountFingerprint &&
|
|
105
|
+
message.claudeCodeCarrier.contentHash === contentHash(message.content) &&
|
|
106
|
+
JSON.stringify(message.claudeCodeCarrier.projection) === JSON.stringify(projection)
|
|
107
|
+
? [{ type: "claude-subscription-directsdk-experimental.native_assistant", ...message.claudeCodeCarrier }]
|
|
108
|
+
: [];
|
|
109
|
+
messages.push({ role: "assistant", content: text, tool_calls: calls, reasoning_details: carrier });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
if (message.content.some((block) => block.type !== "text"))
|
|
113
|
+
throw new Error("Claude Code subscription supports text tool results only");
|
|
114
|
+
messages.push({
|
|
115
|
+
role: "tool",
|
|
116
|
+
tool_call_id: message.toolCallId,
|
|
117
|
+
content: message.content.map((block) => (block.type === "text" ? block.text : "")).join(""),
|
|
118
|
+
is_error: message.isError,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const tools = (context.tools ?? []).map((tool) => ({
|
|
123
|
+
type: "function",
|
|
124
|
+
function: {
|
|
125
|
+
name: tool.name,
|
|
126
|
+
description: tool.description,
|
|
127
|
+
parameters: tool.parameters,
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
return { messages, tools };
|
|
131
|
+
}
|
|
132
|
+
function validateOptions(model, options) {
|
|
133
|
+
if (!options.externalClaudeCode)
|
|
134
|
+
throw new Error("Claude Code subscription setup is required. Run /login anthropic.");
|
|
135
|
+
if (options.apiKey ||
|
|
136
|
+
Object.keys(options.headers ?? {}).length ||
|
|
137
|
+
options.onPayload ||
|
|
138
|
+
options.onResponse ||
|
|
139
|
+
options.env ||
|
|
140
|
+
options.metadata ||
|
|
141
|
+
(options.transport && options.transport !== "auto") ||
|
|
142
|
+
options.maxRetries ||
|
|
143
|
+
(options.maxRetryDelayMs !== undefined && options.maxRetryDelayMs !== 60_000) ||
|
|
144
|
+
options.websocketConnectTimeoutMs ||
|
|
145
|
+
model.baseUrl !== "https://api.anthropic.com") {
|
|
146
|
+
throw new Error("Claude Code subscription does not support API keys, alternate endpoints, or request mutations");
|
|
147
|
+
}
|
|
148
|
+
const anthro = options;
|
|
149
|
+
if ((anthro.toolChoice && anthro.toolChoice !== "auto") ||
|
|
150
|
+
anthro.client ||
|
|
151
|
+
anthro.extraBody ||
|
|
152
|
+
anthro.thinkingBudgetTokens ||
|
|
153
|
+
anthro.stopSequences ||
|
|
154
|
+
anthro.thinkingDisplay !== undefined ||
|
|
155
|
+
anthro.interleavedThinking !== undefined) {
|
|
156
|
+
throw new Error("Claude Code subscription does not support forced tools or native request overrides");
|
|
157
|
+
}
|
|
158
|
+
if (options.temperature !== undefined)
|
|
159
|
+
throw new Error("Claude Code subscription does not support sampling overrides");
|
|
160
|
+
}
|
|
161
|
+
function parseWorkerRecord(line, requestId) {
|
|
162
|
+
if (Buffer.byteLength(line) > MAX_RECORD)
|
|
163
|
+
throw new Error("Claude Code bridge record exceeds size limit");
|
|
164
|
+
const value = JSON.parse(line);
|
|
165
|
+
if (!value ||
|
|
166
|
+
typeof value !== "object" ||
|
|
167
|
+
!("v" in value) ||
|
|
168
|
+
value.v !== 1 ||
|
|
169
|
+
!("requestId" in value) ||
|
|
170
|
+
value.requestId !== requestId ||
|
|
171
|
+
!("type" in value) ||
|
|
172
|
+
typeof value.type !== "string") {
|
|
173
|
+
throw new Error("Invalid Claude Code bridge record");
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
export async function stopNative(pid) {
|
|
178
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
179
|
+
return;
|
|
180
|
+
if (process.platform === "win32") {
|
|
181
|
+
await new Promise((resolve) => {
|
|
182
|
+
const killer = spawn("taskkill", ["/F", "/T", "/PID", String(pid)], { stdio: "ignore", windowsHide: true });
|
|
183
|
+
const timer = setTimeout(() => {
|
|
184
|
+
killer.kill();
|
|
185
|
+
resolve();
|
|
186
|
+
}, 3000);
|
|
187
|
+
killer.once("error", () => {
|
|
188
|
+
clearTimeout(timer);
|
|
189
|
+
resolve();
|
|
190
|
+
});
|
|
191
|
+
killer.once("close", () => {
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
resolve();
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
try {
|
|
199
|
+
process.kill(-pid, "SIGKILL");
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
/* Already exited. */
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function validatedCompletion(value, model, text, thinking) {
|
|
207
|
+
if (!value || typeof value !== "object" || !("choices" in value) || !Array.isArray(value.choices))
|
|
208
|
+
throw new Error("Invalid native completion");
|
|
209
|
+
const response = value;
|
|
210
|
+
const choice = response.choices[0];
|
|
211
|
+
if (!choice ||
|
|
212
|
+
!["stop", "length", "tool_calls"].includes(String(choice.finish_reason)) ||
|
|
213
|
+
(typeof choice.message?.content !== "string" && choice.message?.content !== null) ||
|
|
214
|
+
(choice.message.content ?? "") !== text ||
|
|
215
|
+
(choice.message.reasoning_content ?? "") !== thinking)
|
|
216
|
+
throw new Error("Native completion differs from streamed content");
|
|
217
|
+
const rawCalls = choice.message.tool_calls ?? [];
|
|
218
|
+
if (!Array.isArray(rawCalls))
|
|
219
|
+
throw new Error("Invalid native tool calls");
|
|
220
|
+
const calls = rawCalls.map((call) => {
|
|
221
|
+
if (!call ||
|
|
222
|
+
typeof call !== "object" ||
|
|
223
|
+
typeof call.id !== "string" ||
|
|
224
|
+
typeof call.function?.name !== "string" ||
|
|
225
|
+
typeof call.function?.arguments !== "string")
|
|
226
|
+
throw new Error("Invalid native tool call");
|
|
227
|
+
const args = JSON.parse(call.function.arguments);
|
|
228
|
+
if (!args || typeof args !== "object" || Array.isArray(args))
|
|
229
|
+
throw new Error("Invalid native tool arguments");
|
|
230
|
+
return { type: "toolCall", id: call.id, name: call.function.name, arguments: args };
|
|
231
|
+
});
|
|
232
|
+
const native = response.usage;
|
|
233
|
+
if (!native ||
|
|
234
|
+
typeof native.native_admission !== "object" ||
|
|
235
|
+
native.native_admission === null ||
|
|
236
|
+
native.native_admission.upstream_requests !== 1)
|
|
237
|
+
throw new Error("Native one-request admission not verified");
|
|
238
|
+
if (!native ||
|
|
239
|
+
!Number.isFinite(native.prompt_tokens) ||
|
|
240
|
+
!Number.isFinite(native.completion_tokens) ||
|
|
241
|
+
!Number.isFinite(native.cache_creation_input_tokens) ||
|
|
242
|
+
!("prompt_tokens_details" in native))
|
|
243
|
+
throw new Error("Missing native usage");
|
|
244
|
+
const cached = native.prompt_tokens_details.cached_tokens;
|
|
245
|
+
if (!Number.isFinite(cached))
|
|
246
|
+
throw new Error("Missing native cache usage");
|
|
247
|
+
const usage = {
|
|
248
|
+
input: Number(native.prompt_tokens) - Number(cached) - Number(native.cache_creation_input_tokens),
|
|
249
|
+
output: Number(native.completion_tokens),
|
|
250
|
+
cacheRead: Number(cached),
|
|
251
|
+
cacheWrite: Number(native.cache_creation_input_tokens),
|
|
252
|
+
totalTokens: Number(native.total_tokens),
|
|
253
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
254
|
+
};
|
|
255
|
+
if (usage.input < 0 || usage.output < 0)
|
|
256
|
+
throw new Error("Invalid native usage");
|
|
257
|
+
calculateCost(model, usage);
|
|
258
|
+
const details = choice.message.reasoning_details;
|
|
259
|
+
const carrier = Array.isArray(details)
|
|
260
|
+
? details.find((entry) => entry?.type === "claude-subscription-directsdk-experimental.native_assistant")
|
|
261
|
+
: undefined;
|
|
262
|
+
const projection = {
|
|
263
|
+
content: text.trim(),
|
|
264
|
+
tool_calls: calls.map((call) => ({ id: call.id, name: call.name, input: call.arguments })),
|
|
265
|
+
};
|
|
266
|
+
if (!carrier ||
|
|
267
|
+
carrier.version !== 1 ||
|
|
268
|
+
!Array.isArray(carrier.messages) ||
|
|
269
|
+
JSON.stringify(carrier.projection) !== JSON.stringify(projection))
|
|
270
|
+
throw new Error("Invalid native replay carrier");
|
|
271
|
+
return {
|
|
272
|
+
content: [...(text ? [{ type: "text", text }] : []), ...calls],
|
|
273
|
+
carrier: { version: 1, messages: carrier.messages, projection },
|
|
274
|
+
usage,
|
|
275
|
+
responseId: String(response.id ?? ""),
|
|
276
|
+
reason: choice.finish_reason === "tool_calls" ? "toolUse" : choice.finish_reason === "length" ? "length" : "stop",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
export function streamClaudeCode(model, context, options = {}) {
|
|
280
|
+
const stream = new AssistantMessageEventStream();
|
|
281
|
+
const output = {
|
|
282
|
+
role: "assistant",
|
|
283
|
+
api: model.api,
|
|
284
|
+
provider: model.provider,
|
|
285
|
+
model: model.id,
|
|
286
|
+
content: [],
|
|
287
|
+
usage: {
|
|
288
|
+
input: 0,
|
|
289
|
+
output: 0,
|
|
290
|
+
cacheRead: 0,
|
|
291
|
+
cacheWrite: 0,
|
|
292
|
+
totalTokens: 0,
|
|
293
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
294
|
+
},
|
|
295
|
+
stopReason: "stop",
|
|
296
|
+
timestamp: Date.now(),
|
|
297
|
+
};
|
|
298
|
+
void (async () => {
|
|
299
|
+
let child;
|
|
300
|
+
let nativePid;
|
|
301
|
+
let nativeCleanup;
|
|
302
|
+
const stopNativeOnce = () => {
|
|
303
|
+
if (nativePid === undefined)
|
|
304
|
+
return Promise.resolve();
|
|
305
|
+
nativeCleanup ??= stopNative(nativePid);
|
|
306
|
+
return nativeCleanup;
|
|
307
|
+
};
|
|
308
|
+
let timer;
|
|
309
|
+
let abortTimer;
|
|
310
|
+
let started = false;
|
|
311
|
+
let complete = false;
|
|
312
|
+
let buffer = "";
|
|
313
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
314
|
+
let text = "";
|
|
315
|
+
let thinking = "";
|
|
316
|
+
let activeIndex = -1;
|
|
317
|
+
let activeType;
|
|
318
|
+
try {
|
|
319
|
+
validateOptions(model, options);
|
|
320
|
+
if (options.signal?.aborted)
|
|
321
|
+
throw new Error("Claude Code request cancelled");
|
|
322
|
+
const connection = options.externalClaudeCode;
|
|
323
|
+
const env = claudeCodeEnvironment(connection.configDir);
|
|
324
|
+
const input = history(context, model, connection);
|
|
325
|
+
const anthropicOptions = options;
|
|
326
|
+
const reasoning = "reasoning" in options && options.reasoning
|
|
327
|
+
? { enabled: true, effort: options.reasoning }
|
|
328
|
+
: anthropicOptions.thinkingEnabled !== undefined || anthropicOptions.effort
|
|
329
|
+
? {
|
|
330
|
+
enabled: anthropicOptions.thinkingEnabled ?? true,
|
|
331
|
+
...(anthropicOptions.effort ? { effort: anthropicOptions.effort } : {}),
|
|
332
|
+
}
|
|
333
|
+
: undefined;
|
|
334
|
+
const requestId = randomUUID();
|
|
335
|
+
child = spawn(connection.python, ["-s", "-B", "-u", WORKER], {
|
|
336
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
337
|
+
windowsHide: true,
|
|
338
|
+
env,
|
|
339
|
+
});
|
|
340
|
+
const worker = child;
|
|
341
|
+
worker.stderr.resume();
|
|
342
|
+
worker.stdin.on("error", () => { });
|
|
343
|
+
const idleMs = Math.min(300_000, Math.max(1000, options.timeoutMs ?? 180_000));
|
|
344
|
+
const record = {
|
|
345
|
+
v: 1,
|
|
346
|
+
requestId,
|
|
347
|
+
type: "start",
|
|
348
|
+
command: connection.command,
|
|
349
|
+
env,
|
|
350
|
+
model: model.id,
|
|
351
|
+
...input,
|
|
352
|
+
extraBody: {
|
|
353
|
+
...(options.maxTokens ? { max_tokens: options.maxTokens } : {}),
|
|
354
|
+
...(reasoning ? { reasoning } : {}),
|
|
355
|
+
},
|
|
356
|
+
timeout: Math.round(idleMs / 1000),
|
|
357
|
+
};
|
|
358
|
+
const payload = JSON.stringify(record);
|
|
359
|
+
if (Buffer.byteLength(payload) > MAX_RECORD)
|
|
360
|
+
throw new Error("Claude Code request exceeds size limit");
|
|
361
|
+
worker.stdin.write(`${payload}\n`);
|
|
362
|
+
let cancelSent = false;
|
|
363
|
+
let timedOut = false;
|
|
364
|
+
const abort = () => {
|
|
365
|
+
if (cancelSent)
|
|
366
|
+
return;
|
|
367
|
+
cancelSent = true;
|
|
368
|
+
if (timer)
|
|
369
|
+
clearTimeout(timer);
|
|
370
|
+
worker.stdin.write(`${JSON.stringify({ v: 1, requestId, type: "cancel" })}\n`);
|
|
371
|
+
abortTimer = setTimeout(() => {
|
|
372
|
+
void stopNativeOnce().then(() => worker.kill());
|
|
373
|
+
}, 2500);
|
|
374
|
+
};
|
|
375
|
+
const resetDeadline = () => {
|
|
376
|
+
if (cancelSent)
|
|
377
|
+
return;
|
|
378
|
+
if (timer)
|
|
379
|
+
clearTimeout(timer);
|
|
380
|
+
timer = setTimeout(() => {
|
|
381
|
+
timedOut = true;
|
|
382
|
+
abort();
|
|
383
|
+
}, idleMs);
|
|
384
|
+
};
|
|
385
|
+
timer = setTimeout(() => {
|
|
386
|
+
timedOut = true;
|
|
387
|
+
abort();
|
|
388
|
+
}, Math.min(30_000, idleMs));
|
|
389
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
390
|
+
if (options.signal?.aborted)
|
|
391
|
+
abort();
|
|
392
|
+
try {
|
|
393
|
+
await new Promise((resolve, reject) => {
|
|
394
|
+
worker.on("error", reject);
|
|
395
|
+
worker.stdout.on("data", (data) => {
|
|
396
|
+
try {
|
|
397
|
+
buffer += decoder.decode(data, { stream: true });
|
|
398
|
+
if (Buffer.byteLength(buffer) > MAX_RECORD)
|
|
399
|
+
throw new Error("Claude Code bridge record exceeds size limit");
|
|
400
|
+
for (let newline = buffer.indexOf("\n"); newline >= 0; newline = buffer.indexOf("\n")) {
|
|
401
|
+
const event = parseWorkerRecord(buffer.slice(0, newline), requestId);
|
|
402
|
+
buffer = buffer.slice(newline + 1);
|
|
403
|
+
if (event.type === "ready" || event.type === "replay_progress")
|
|
404
|
+
resetDeadline();
|
|
405
|
+
else if (event.type === "native_started" &&
|
|
406
|
+
Number.isSafeInteger(event.pid) &&
|
|
407
|
+
Number(event.pid) > 0) {
|
|
408
|
+
nativePid = Number(event.pid);
|
|
409
|
+
resetDeadline();
|
|
410
|
+
}
|
|
411
|
+
else if (cancelSent &&
|
|
412
|
+
["start", "text_delta", "thinking_delta", "complete"].includes(event.type))
|
|
413
|
+
continue;
|
|
414
|
+
else if (event.type === "start") {
|
|
415
|
+
if (started)
|
|
416
|
+
throw new Error("Duplicate Claude Code stream start");
|
|
417
|
+
started = true;
|
|
418
|
+
stream.push({ type: "start", partial: output });
|
|
419
|
+
}
|
|
420
|
+
else if (event.type === "text_delta" || event.type === "thinking_delta") {
|
|
421
|
+
if (!started || complete || typeof event.text !== "string")
|
|
422
|
+
throw new Error("Invalid Claude Code stream delta");
|
|
423
|
+
resetDeadline();
|
|
424
|
+
const kind = event.type === "text_delta" ? "text" : "thinking";
|
|
425
|
+
if (activeType !== kind) {
|
|
426
|
+
if (activeType === "text")
|
|
427
|
+
stream.push({
|
|
428
|
+
type: "text_end",
|
|
429
|
+
contentIndex: activeIndex,
|
|
430
|
+
content: output.content[activeIndex].text,
|
|
431
|
+
partial: output,
|
|
432
|
+
});
|
|
433
|
+
if (activeType === "thinking")
|
|
434
|
+
stream.push({
|
|
435
|
+
type: "thinking_end",
|
|
436
|
+
contentIndex: activeIndex,
|
|
437
|
+
content: output.content[activeIndex].thinking,
|
|
438
|
+
partial: output,
|
|
439
|
+
});
|
|
440
|
+
activeIndex = output.content.length;
|
|
441
|
+
activeType = kind;
|
|
442
|
+
if (kind === "text") {
|
|
443
|
+
output.content.push({ type: "text", text: "" });
|
|
444
|
+
stream.push({ type: "text_start", contentIndex: activeIndex, partial: output });
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
output.content.push({ type: "thinking", thinking: "" });
|
|
448
|
+
stream.push({ type: "thinking_start", contentIndex: activeIndex, partial: output });
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
const block = output.content[activeIndex];
|
|
452
|
+
if (kind === "text" && block?.type === "text") {
|
|
453
|
+
text += event.text;
|
|
454
|
+
block.text += event.text;
|
|
455
|
+
stream.push({
|
|
456
|
+
type: "text_delta",
|
|
457
|
+
contentIndex: activeIndex,
|
|
458
|
+
delta: event.text,
|
|
459
|
+
partial: output,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
if (kind === "thinking" && block?.type === "thinking") {
|
|
463
|
+
thinking += event.text;
|
|
464
|
+
block.thinking += event.text;
|
|
465
|
+
stream.push({
|
|
466
|
+
type: "thinking_delta",
|
|
467
|
+
contentIndex: activeIndex,
|
|
468
|
+
delta: event.text,
|
|
469
|
+
partial: output,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
else if (event.type === "complete") {
|
|
474
|
+
if (!started || complete)
|
|
475
|
+
throw new Error("Invalid Claude Code completion order");
|
|
476
|
+
const result = validatedCompletion(event.response, model, text, thinking);
|
|
477
|
+
complete = true;
|
|
478
|
+
if (activeType === "text")
|
|
479
|
+
stream.push({
|
|
480
|
+
type: "text_end",
|
|
481
|
+
contentIndex: activeIndex,
|
|
482
|
+
content: output.content[activeIndex].text,
|
|
483
|
+
partial: output,
|
|
484
|
+
});
|
|
485
|
+
if (activeType === "thinking")
|
|
486
|
+
stream.push({
|
|
487
|
+
type: "thinking_end",
|
|
488
|
+
contentIndex: activeIndex,
|
|
489
|
+
content: output.content[activeIndex].thinking,
|
|
490
|
+
partial: output,
|
|
491
|
+
});
|
|
492
|
+
output.content = [
|
|
493
|
+
...output.content.filter((block) => block.type !== "toolCall"),
|
|
494
|
+
...result.content.filter((block) => block.type === "toolCall"),
|
|
495
|
+
];
|
|
496
|
+
output.claudeCodeCarrier = result.carrier && {
|
|
497
|
+
...result.carrier,
|
|
498
|
+
contentHash: contentHash(output.content),
|
|
499
|
+
accountFingerprint: connection.accountFingerprint,
|
|
500
|
+
};
|
|
501
|
+
output.responseId = result.responseId;
|
|
502
|
+
output.usage = result.usage;
|
|
503
|
+
output.stopReason = result.reason;
|
|
504
|
+
for (let index = 0; index < output.content.length; index++) {
|
|
505
|
+
const block = output.content[index];
|
|
506
|
+
if (block.type === "toolCall") {
|
|
507
|
+
stream.push({ type: "toolcall_start", contentIndex: index, partial: output });
|
|
508
|
+
stream.push({
|
|
509
|
+
type: "toolcall_end",
|
|
510
|
+
contentIndex: index,
|
|
511
|
+
toolCall: block,
|
|
512
|
+
partial: output,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
else if (event.type === "cancelled" && cancelSent) {
|
|
518
|
+
}
|
|
519
|
+
else if (event.type === "error" || event.type === "cancelled")
|
|
520
|
+
throw new Error("Claude Code request failed. Check subscription setup and native CLI compatibility.");
|
|
521
|
+
else
|
|
522
|
+
throw new Error("Unknown Claude Code bridge event");
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
reject(error);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
worker.on("close", () => {
|
|
530
|
+
try {
|
|
531
|
+
decoder.decode();
|
|
532
|
+
if (complete || cancelSent)
|
|
533
|
+
resolve();
|
|
534
|
+
else
|
|
535
|
+
reject(new Error("Claude Code bridge exited without a validated completion"));
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
reject(error);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
finally {
|
|
544
|
+
options.signal?.removeEventListener("abort", abort);
|
|
545
|
+
}
|
|
546
|
+
if (options.signal?.aborted || timedOut)
|
|
547
|
+
throw new Error("Claude Code request cancelled or timed out");
|
|
548
|
+
stream.push({ type: "done", reason: output.stopReason, message: output });
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
output.content = [];
|
|
552
|
+
output.claudeCodeCarrier = undefined;
|
|
553
|
+
output.stopReason = options.signal?.aborted ? "aborted" : "error";
|
|
554
|
+
const detail = error instanceof Error &&
|
|
555
|
+
(error.message.startsWith("Claude Code subscription setup is required") ||
|
|
556
|
+
error.message.startsWith("Claude Code subscription does not support") ||
|
|
557
|
+
error.message.startsWith("Claude Code subscription refuses conflicting") ||
|
|
558
|
+
error.message.startsWith("Claude Code subscription requires TLS"))
|
|
559
|
+
? error.message
|
|
560
|
+
: undefined;
|
|
561
|
+
output.errorMessage = options.signal?.aborted
|
|
562
|
+
? "Claude Code request cancelled"
|
|
563
|
+
: (detail ??
|
|
564
|
+
"Claude Code request failed. Run /login anthropic to check setup and Claude Code version 2.1.263.");
|
|
565
|
+
stream.push({ type: "error", reason: output.stopReason, error: output });
|
|
566
|
+
}
|
|
567
|
+
finally {
|
|
568
|
+
if (timer)
|
|
569
|
+
clearTimeout(timer);
|
|
570
|
+
if (abortTimer)
|
|
571
|
+
clearTimeout(abortTimer);
|
|
572
|
+
if (child) {
|
|
573
|
+
if (!complete || child.exitCode === null)
|
|
574
|
+
await stopNativeOnce();
|
|
575
|
+
if (child.exitCode === null)
|
|
576
|
+
child.kill();
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
})();
|
|
580
|
+
return stream;
|
|
581
|
+
}
|
|
582
|
+
//# sourceMappingURL=anthropic-claude-code-bridge.js.map
|