@cryer/star-cli 0.3.12 → 0.3.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -5
- package/README.zh-CN.md +15 -5
- package/dist/{chunk-LSYGHLRU.js → chunk-2ISBRSBA.js} +479 -296
- package/dist/chunk-2ISBRSBA.js.map +1 -0
- package/dist/{chunk-F5RWZMQY.js → chunk-4HANB3DJ.js} +458 -221
- package/dist/chunk-4HANB3DJ.js.map +1 -0
- package/dist/chunk-4IRY44TG.js +0 -0
- package/dist/{chunk-Q5FJN4LN.js → chunk-54KH4JSO.js} +213 -125
- package/dist/chunk-54KH4JSO.js.map +1 -0
- package/dist/{chunk-MYHD3SZB.js → chunk-CECMCDZG.js} +43 -30
- package/dist/chunk-CECMCDZG.js.map +1 -0
- package/dist/{chunk-MVAMPVTA.js → chunk-KILMI3S5.js} +13 -5
- package/dist/{chunk-MVAMPVTA.js.map → chunk-KILMI3S5.js.map} +1 -1
- package/dist/{chunk-2GAUAYCU.js → chunk-NRLGGA7L.js} +507 -175
- package/dist/chunk-NRLGGA7L.js.map +1 -0
- package/dist/loop-M64D5YII.js +17 -0
- package/dist/main.js +177 -131
- package/dist/main.js.map +1 -1
- package/dist/{provider-EWMUPBLH.js → provider-3Z4B7RMF.js} +2 -2
- package/dist/{repl-SSCURTAH.js → repl-N2H7JDSH.js} +617 -327
- package/dist/repl-N2H7JDSH.js.map +1 -0
- package/dist/{tools-BH55BVIS.js → tools-5RKM7ZI3.js} +3 -3
- package/package.json +16 -25
- package/dist/chunk-2GAUAYCU.js.map +0 -1
- package/dist/chunk-F5RWZMQY.js.map +0 -1
- package/dist/chunk-LSYGHLRU.js.map +0 -1
- package/dist/chunk-MYHD3SZB.js.map +0 -1
- package/dist/chunk-Q5FJN4LN.js.map +0 -1
- package/dist/loop-BYXBNFFY.js +0 -17
- package/dist/repl-SSCURTAH.js.map +0 -1
- /package/dist/{loop-BYXBNFFY.js.map → loop-M64D5YII.js.map} +0 -0
- /package/dist/{provider-EWMUPBLH.js.map → provider-3Z4B7RMF.js.map} +0 -0
- /package/dist/{tools-BH55BVIS.js.map → tools-5RKM7ZI3.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/llm/provider.ts","../src/llm/activity.ts","../src/llm/sse-normalize.ts"],"sourcesContent":["import { createAnthropic } from \"@ai-sdk/anthropic\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport { createOpenAICompatible } from \"@ai-sdk/openai-compatible\";\nimport type { LanguageModel } from \"ai\";\nimport type { ProviderConfig, StarConfig } from \"../config/schema\";\nimport { wrapFetchWithActivity } from \"./activity\";\nimport { resolveModelConfig } from \"./registry\";\nimport { createSseNormalizingFetch } from \"./sse-normalize\";\n\nexport type ProviderMetadata = Record<string, Record<string, unknown>>;\n\nfunction resolveApiKey(provider: ProviderConfig): string {\n const fromEnv = provider.apiKeyEnv ? process.env[provider.apiKeyEnv] : undefined;\n const apiKey = fromEnv ?? provider.apiKey;\n if (!apiKey) {\n const hint = provider.apiKeyEnv\n ? `set ${provider.apiKeyEnv} or configure apiKey`\n : \"configure apiKey\";\n throw new Error(`Missing API key for provider \"${provider.name}\": ${hint}`);\n }\n return apiKey;\n}\n\nexport function createModel(config: StarConfig, modelName?: string): LanguageModel {\n const modelConfig = resolveModelConfig(config, modelName);\n const provider = config.providers.find((p) => p.name === modelConfig.provider);\n if (!provider) {\n const available = config.providers.map((p) => p.name).join(\", \") || \"(none)\";\n throw new Error(\n `Provider \"${modelConfig.provider}\" for model \"${modelConfig.name}\" not found. Available providers: ${available}`,\n );\n }\n const apiKey = resolveApiKey(provider);\n // Every protocol goes through the activity-tracking fetch so the stream\n // watchdog can see raw byte liveness (sse-normalize passes init through,\n // so the AbortSignal key survives the composition).\n const trackedFetch = wrapFetchWithActivity();\n switch (provider.protocol) {\n case \"anthropic\":\n return createAnthropic({\n baseURL: provider.baseURL,\n apiKey,\n headers: provider.headers,\n fetch: trackedFetch,\n })(modelConfig.model);\n case \"openai-compatible\":\n // The per-model config asks the relay for a terminal usage chunk\n // (stream_options.include_usage): relays following OpenAI semantics\n // (DeepSeek, Moonshot) omit usage from streams without it, which left\n // /cost, /usage and the status bar reading zero.\n return createOpenAICompatible({\n name: provider.name,\n baseURL: provider.baseURL,\n apiKey,\n headers: provider.headers,\n fetch: trackedFetch,\n })(modelConfig.model, {}, { includeUsage: true });\n case \"openai-responses\":\n return createOpenAI({\n baseURL: provider.baseURL,\n apiKey,\n headers: provider.headers,\n // Relays fronting the Responses API sometimes batch several JSON\n // events into one SSE data payload, which the SDK's parser cannot\n // read; normalize the wire format back to one event per data line.\n fetch: createSseNormalizingFetch(trackedFetch),\n }).responses(modelConfig.model);\n }\n}\n\n// Builds the per-call provider metadata carrying the model's configured\n// reasoningEffort. The metadata key must equal the provider instance name:\n// \"openai\" for the responses-protocol provider (createOpenAI fixes it), the\n// provider's own name for openai-compatible (createOpenAICompatible names the\n// instance after it). Anthropic maps effort to a thinking budget rather than\n// an effort enum — unsupported, so no metadata is produced for it. Returns\n// undefined when the model sets no reasoningEffort (request unchanged).\nexport function reasoningEffortMetadata(\n config: StarConfig,\n modelName?: string,\n): ProviderMetadata | undefined {\n const modelConfig = resolveModelConfig(config, modelName);\n if (!modelConfig.reasoningEffort) return undefined;\n const provider = config.providers.find((p) => p.name === modelConfig.provider);\n if (!provider || provider.protocol === \"anthropic\") return undefined;\n const key = provider.protocol === \"openai-responses\" ? \"openai\" : provider.name;\n return { [key]: { reasoningEffort: modelConfig.reasoningEffort } };\n}\n","// Byte-level liveness for streamed responses. The idle watchdog in stream.ts\n// only sees parsed stream parts, but relays fronting slow upstreams often\n// send SSE heartbeat comments (\": keep-alive\") while buffering — the AI SDK\n// parses and swallows those, so at the part level a healthy-but-slow stream\n// looks dead and gets cut mid-generation. Wrapping fetch here timestamps\n// every raw body chunk per request (keyed by the request's AbortSignal,\n// which the AI SDK forwards from streamText's abortSignal), letting the\n// watchdog tell \"bytes still flowing\" apart from \"connection truly silent\".\n\nexport interface StreamActivity {\n // Timestamp of the last raw body chunk (or response headers, initially).\n lastChunkAt: number;\n}\n\nconst trackers = new WeakMap<AbortSignal, StreamActivity>();\n\nexport function streamActivity(signal: AbortSignal): StreamActivity | undefined {\n return trackers.get(signal);\n}\n\n// Registers a tracker for a signal. Production code should never call this —\n// wrapFetchWithActivity registers on its own; this exists so tests can fake\n// byte-level liveness for a streamChat call without going through fetch.\nexport function registerStreamActivity(signal: AbortSignal): StreamActivity {\n const tracker: StreamActivity = { lastChunkAt: Date.now() };\n trackers.set(signal, tracker);\n return tracker;\n}\n\nexport function wrapFetchWithActivity(baseFetch: typeof fetch = fetch): typeof fetch {\n return async (input, init) => {\n const response = await baseFetch(input, init);\n const signal = init?.signal ?? (input instanceof Request ? input.signal : null);\n if (!signal || !response.body) return response;\n const tracker: StreamActivity = { lastChunkAt: Date.now() };\n trackers.set(signal, tracker);\n const reader = response.body.getReader();\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n return;\n }\n tracker.lastChunkAt = Date.now();\n controller.enqueue(value);\n } catch (error) {\n controller.error(error);\n }\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n };\n}\n","// Some relays (this one fronts the OpenAI Responses API behind\n// /v1/responses) batch several compact JSON events into ONE SSE data\n// payload instead of one event per `data:` line — either as bare\n// continuation lines, or glued together with the next event's\n// `event:`/`data:` line and no newline at all. The AI SDK's SSE parser\n// JSON-parses each data payload whole, so a batched payload fails with\n// \"Unexpected non-whitespace character after JSON\". Codex and opencode\n// parse events line-by-line and tolerate this. Since we stay on the AI SDK,\n// normalize the wire format at the fetch layer: split every batched data\n// payload back into one event per JSON object (the split point is the true\n// end of each object, found by string-aware brace counting).\n//\n// The output is NOT byte-equivalent to the input: every event is re-emitted\n// as `data: <payload>` followed by a blank line, so CRLF endings become LF\n// and a `data:{...}` line gains the space after the colon. Lines are split\n// on \\n only — a stream using bare \\r as its line terminator is not\n// supported.\n\n// Returns [json, rest] when text starts with a complete JSON object,\n// splitting at its true end (string- and escape-aware, so a \"}\" inside a\n// string value never counts), or null when the object is incomplete or the\n// text is not an object. Relays sometimes glue the next event's `event:` /\n// `data:` line onto a JSON payload without any newline — brace counting is\n// the only reliable split point.\nfunction splitJsonPrefix(text: string): [string, string] | null {\n if (!text.startsWith(\"{\")) return null;\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === \"\\\\\") escaped = true;\n else if (ch === '\"') inString = false;\n continue;\n }\n if (ch === '\"') inString = true;\n else if (ch === \"{\") depth += 1;\n else if (ch === \"}\") {\n depth -= 1;\n if (depth === 0) return [text.slice(0, i + 1), text.slice(i + 1).trimStart()];\n }\n }\n return null;\n}\n\n// Splits SSE text into events at blank lines, then re-emits each data\n// payload that holds multiple JSON objects as separate `data:` events.\n// Streaming-safe: lines are buffered across chunks, and a trailing partial\n// line is held until it completes or the stream ends.\nexport class SseNormalizeTransform extends TransformStream<string, string> {\n constructor() {\n let buffer = \"\";\n // Payload of the data field currently being assembled (without the\n // \"data:\" prefix), or null when no event is open.\n let pending: string | null = null;\n\n const flushPending = (controller: TransformStreamDefaultController<string>) => {\n if (pending === null) return;\n controller.enqueue(`data: ${pending}\\n\\n`);\n pending = null;\n };\n\n // Emits a data payload, splitting off any events glued to it without a\n // newline (`{...}event: foo`, `{...}data: {...}`, `{...}{...}`): each\n // complete JSON object becomes its own event and the remainder is\n // reprocessed. An incomplete or non-JSON payload keeps the original\n // assembly behavior (held as pending until the event closes).\n const emitPayload = (payload: string, controller: TransformStreamDefaultController<string>) => {\n let rest = payload;\n for (;;) {\n const split = splitJsonPrefix(rest);\n if (!split) {\n pending = rest;\n return;\n }\n const [json, tail] = split;\n if (tail === \"\") {\n pending = json;\n return;\n }\n controller.enqueue(`data: ${json}\\n\\n`);\n if (tail.startsWith(\"data:\")) {\n rest = tail.slice(5).replace(/^ /, \"\");\n continue;\n }\n if (tail.startsWith(\"{\")) {\n rest = tail;\n continue;\n }\n // A glued `event:`/`id:`/comment line: emit it as its own line —\n // harmless to the SDK parser, which reads the event type from the\n // JSON payload itself.\n controller.enqueue(`${tail}\\n`);\n return;\n }\n };\n\n const handleLine = (raw: string, controller: TransformStreamDefaultController<string>) => {\n const line = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n if (line === \"\") {\n flushPending(controller);\n return;\n }\n if (line.startsWith(\"data:\")) {\n // A new data field closes any event already being assembled.\n flushPending(controller);\n emitPayload(line.slice(5).replace(/^ /, \"\"), controller);\n return;\n }\n if (line.startsWith(\"{\") && pending !== null) {\n // A continuation line of a batched payload: the previous object is\n // complete, so it becomes its own event and this line opens the next.\n flushPending(controller);\n emitPayload(line, controller);\n return;\n }\n // Comments, event:/id:/retry: fields, and anything else: pass through\n // as a standalone line once no data payload is open.\n flushPending(controller);\n controller.enqueue(`${line}\\n`);\n };\n\n super({\n transform(chunk, controller) {\n buffer += chunk;\n let index = buffer.indexOf(\"\\n\");\n while (index !== -1) {\n handleLine(buffer.slice(0, index), controller);\n buffer = buffer.slice(index + 1);\n index = buffer.indexOf(\"\\n\");\n }\n },\n flush(controller) {\n if (buffer.length > 0) handleLine(buffer, controller);\n flushPending(controller);\n },\n });\n }\n}\n\n// Wraps a base fetch (default: global) so SSE responses are piped through\n// SseNormalizeTransform; non-SSE responses (JSON errors, etc.) pass through\n// untouched. Used for the openai-responses protocol only — chat-completions\n// relays already emit one event per data line.\nexport function createSseNormalizingFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return async (input, init) => {\n const response = await baseFetch(input, init);\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (!response.body || !contentType.includes(\"text/event-stream\")) {\n return response;\n }\n const body = response.body\n .pipeThrough(new TextDecoderStream())\n .pipeThrough(new SseNormalizeTransform())\n .pipeThrough(new TextEncoderStream());\n // The rewritten body no longer matches the original bytes, so the old\n // content-length/content-encoding would lie about (and corrupt) it.\n const headers = new Headers(response.headers);\n headers.delete(\"content-length\");\n headers.delete(\"content-encoding\");\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/llm/provider.ts","../src/llm/activity.ts","../src/llm/sse-normalize.ts"],"sourcesContent":["import { createAnthropic } from \"@ai-sdk/anthropic\";\nimport { createOpenAI } from \"@ai-sdk/openai\";\nimport { createOpenAICompatible } from \"@ai-sdk/openai-compatible\";\nimport type { LanguageModel } from \"ai\";\nimport type { ProviderConfig, StarConfig } from \"../config/schema\";\nimport { wrapFetchWithActivity } from \"./activity\";\nimport { resolveModelConfig } from \"./registry\";\nimport { createSseNormalizingFetch } from \"./sse-normalize\";\n\nexport type ProviderMetadata = Record<string, Record<string, unknown>>;\n\nfunction resolveApiKey(provider: ProviderConfig): string {\n const fromEnv = provider.apiKeyEnv ? process.env[provider.apiKeyEnv] : undefined;\n const apiKey = fromEnv ?? provider.apiKey;\n if (!apiKey) {\n const hint = provider.apiKeyEnv\n ? `set ${provider.apiKeyEnv} or configure apiKey`\n : \"configure apiKey\";\n throw new Error(`Missing API key for provider \"${provider.name}\": ${hint}`);\n }\n return apiKey;\n}\n\nexport function createModel(config: StarConfig, modelName?: string): LanguageModel {\n const modelConfig = resolveModelConfig(config, modelName);\n const provider = config.providers.find((p) => p.name === modelConfig.provider);\n if (!provider) {\n const available = config.providers.map((p) => p.name).join(\", \") || \"(none)\";\n throw new Error(\n `Provider \"${modelConfig.provider}\" for model \"${modelConfig.name}\" not found. Available providers: ${available}`,\n );\n }\n const apiKey = resolveApiKey(provider);\n // Every protocol goes through the activity-tracking fetch so the stream\n // watchdog can see raw byte liveness (sse-normalize passes init through,\n // so the AbortSignal key survives the composition).\n const trackedFetch = wrapFetchWithActivity();\n switch (provider.protocol) {\n case \"anthropic\":\n return createAnthropic({\n baseURL: provider.baseURL,\n apiKey,\n headers: provider.headers,\n fetch: trackedFetch,\n })(modelConfig.model);\n case \"openai-compatible\":\n // The per-model config asks the relay for a terminal usage chunk\n // (stream_options.include_usage): relays following OpenAI semantics\n // (DeepSeek, Moonshot) omit usage from streams without it, which left\n // /cost, /usage and the status bar reading zero.\n return createOpenAICompatible({\n name: provider.name,\n baseURL: provider.baseURL,\n apiKey,\n headers: provider.headers,\n fetch: trackedFetch,\n })(modelConfig.model, {}, { includeUsage: true });\n case \"openai-responses\":\n return createOpenAI({\n baseURL: provider.baseURL,\n apiKey,\n headers: provider.headers,\n // Relays fronting the Responses API sometimes batch several JSON\n // events into one SSE data payload, which the SDK's parser cannot\n // read; normalize the wire format back to one event per data line.\n fetch: createSseNormalizingFetch(trackedFetch),\n }).responses(modelConfig.model);\n }\n}\n\n// Builds the per-call provider metadata carrying the model's configured\n// reasoningEffort. The metadata key must equal the provider instance name:\n// \"openai\" for the responses-protocol provider (createOpenAI fixes it), the\n// provider's own name for openai-compatible (createOpenAICompatible names the\n// instance after it). Anthropic maps effort to a thinking budget rather than\n// an effort enum — unsupported, so no metadata is produced for it. Returns\n// undefined when the model sets no reasoningEffort (request unchanged).\nexport function reasoningEffortMetadata(\n config: StarConfig,\n modelName?: string,\n): ProviderMetadata | undefined {\n const modelConfig = resolveModelConfig(config, modelName);\n if (!modelConfig.reasoningEffort) return undefined;\n const provider = config.providers.find((p) => p.name === modelConfig.provider);\n if (!provider || provider.protocol === \"anthropic\") return undefined;\n const key = provider.protocol === \"openai-responses\" ? \"openai\" : provider.name;\n return { [key]: { reasoningEffort: modelConfig.reasoningEffort } };\n}\n","// Byte-level liveness for streamed responses. The idle watchdog in stream.ts\n// only sees parsed stream parts, but relays fronting slow upstreams often\n// send SSE heartbeat comments (\": keep-alive\") while buffering — the AI SDK\n// parses and swallows those, so at the part level a healthy-but-slow stream\n// looks dead and gets cut mid-generation. Wrapping fetch here timestamps\n// every raw body chunk per request (keyed by the request's AbortSignal,\n// which the AI SDK forwards from streamText's abortSignal), letting the\n// watchdog tell \"bytes still flowing\" apart from \"connection truly silent\".\n\nexport interface StreamActivity {\n // Timestamp of the last raw body chunk (or response headers, initially).\n lastChunkAt: number;\n}\n\nconst trackers = new WeakMap<AbortSignal, StreamActivity>();\n\nexport function streamActivity(signal: AbortSignal): StreamActivity | undefined {\n return trackers.get(signal);\n}\n\n// Registers a tracker for a signal. Production code should never call this —\n// wrapFetchWithActivity registers on its own; this exists so tests can fake\n// byte-level liveness for a streamChat call without going through fetch.\nexport function registerStreamActivity(signal: AbortSignal): StreamActivity {\n const tracker: StreamActivity = { lastChunkAt: Date.now() };\n trackers.set(signal, tracker);\n return tracker;\n}\n\nexport function wrapFetchWithActivity(baseFetch: typeof fetch = fetch): typeof fetch {\n return async (input, init) => {\n const response = await baseFetch(input, init);\n const signal = init?.signal ?? (input instanceof Request ? input.signal : null);\n if (!signal || !response.body) return response;\n const tracker: StreamActivity = { lastChunkAt: Date.now() };\n trackers.set(signal, tracker);\n const reader = response.body.getReader();\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n return;\n }\n tracker.lastChunkAt = Date.now();\n controller.enqueue(value);\n } catch (error) {\n controller.error(error);\n }\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers: response.headers,\n });\n };\n}\n","// Some relays (this one fronts the OpenAI Responses API behind\n// /v1/responses) batch several compact JSON events into ONE SSE data\n// payload instead of one event per `data:` line — either as bare\n// continuation lines, or glued together with the next event's\n// `event:`/`data:` line and no newline at all. The AI SDK's SSE parser\n// JSON-parses each data payload whole, so a batched payload fails with\n// \"Unexpected non-whitespace character after JSON\". Codex and opencode\n// parse events line-by-line and tolerate this. Since we stay on the AI SDK,\n// normalize the wire format at the fetch layer: split every batched data\n// payload back into one event per JSON object (the split point is the true\n// end of each object, found by string-aware brace counting).\n//\n// The output is NOT byte-equivalent to the input: every event is re-emitted\n// as `data: <payload>` followed by a blank line, so CRLF endings become LF\n// and a `data:{...}` line gains the space after the colon. Lines are split\n// on \\n only — a stream using bare \\r as its line terminator is not\n// supported.\n\n// Returns [json, rest] when text starts with a complete JSON object,\n// splitting at its true end (string- and escape-aware, so a \"}\" inside a\n// string value never counts), or null when the object is incomplete or the\n// text is not an object. Relays sometimes glue the next event's `event:` /\n// `data:` line onto a JSON payload without any newline — brace counting is\n// the only reliable split point.\nfunction splitJsonPrefix(text: string): [string, string] | null {\n if (!text.startsWith(\"{\")) return null;\n let depth = 0;\n let inString = false;\n let escaped = false;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n if (inString) {\n if (escaped) escaped = false;\n else if (ch === \"\\\\\") escaped = true;\n else if (ch === '\"') inString = false;\n continue;\n }\n if (ch === '\"') inString = true;\n else if (ch === \"{\") depth += 1;\n else if (ch === \"}\") {\n depth -= 1;\n if (depth === 0) return [text.slice(0, i + 1), text.slice(i + 1).trimStart()];\n }\n }\n return null;\n}\n\n// Splits SSE text into events at blank lines, then re-emits each data\n// payload that holds multiple JSON objects as separate `data:` events.\n// Streaming-safe: lines are buffered across chunks, and a trailing partial\n// line is held until it completes or the stream ends.\nexport class SseNormalizeTransform extends TransformStream<string, string> {\n constructor() {\n let buffer = \"\";\n // Payload of the data field currently being assembled (without the\n // \"data:\" prefix), or null when no event is open.\n let pending: string | null = null;\n\n const flushPending = (controller: TransformStreamDefaultController<string>) => {\n if (pending === null) return;\n controller.enqueue(`data: ${pending}\\n\\n`);\n pending = null;\n };\n\n // Emits a data payload, splitting off any events glued to it without a\n // newline (`{...}event: foo`, `{...}data: {...}`, `{...}{...}`): each\n // complete JSON object becomes its own event and the remainder is\n // reprocessed. An incomplete or non-JSON payload keeps the original\n // assembly behavior (held as pending until the event closes).\n const emitPayload = (payload: string, controller: TransformStreamDefaultController<string>) => {\n let rest = payload;\n for (;;) {\n const split = splitJsonPrefix(rest);\n if (!split) {\n pending = rest;\n return;\n }\n const [json, tail] = split;\n if (tail === \"\") {\n pending = json;\n return;\n }\n controller.enqueue(`data: ${json}\\n\\n`);\n if (tail.startsWith(\"data:\")) {\n rest = tail.slice(5).replace(/^ /, \"\");\n continue;\n }\n if (tail.startsWith(\"{\")) {\n rest = tail;\n continue;\n }\n // A glued `event:`/`id:`/comment line: emit it as its own line —\n // harmless to the SDK parser, which reads the event type from the\n // JSON payload itself.\n controller.enqueue(`${tail}\\n`);\n return;\n }\n };\n\n const handleLine = (raw: string, controller: TransformStreamDefaultController<string>) => {\n const line = raw.endsWith(\"\\r\") ? raw.slice(0, -1) : raw;\n if (line === \"\") {\n flushPending(controller);\n return;\n }\n if (line.startsWith(\"data:\")) {\n const payload = line.slice(5).replace(/^ /, \"\");\n // Standard SSE joins repeated data fields with a newline. A complete\n // JSON object followed by another one is instead the relay's batched\n // shape, so only an incomplete payload absorbs the next data field.\n if (pending !== null && splitJsonPrefix(pending)) flushPending(controller);\n if (pending === null) emitPayload(payload, controller);\n else emitPayload(`${pending}\\n${payload}`, controller);\n return;\n }\n if (line.startsWith(\"{\") && pending !== null) {\n if (splitJsonPrefix(pending)) {\n // A continuation line of a batched payload: the previous object is\n // complete, so it becomes its own event and this line opens the next.\n flushPending(controller);\n emitPayload(line, controller);\n } else {\n emitPayload(`${pending}\\n${line}`, controller);\n }\n return;\n }\n // Comments, event:/id:/retry: fields, and anything else: pass through\n // as a standalone line once no data payload is open.\n flushPending(controller);\n controller.enqueue(`${line}\\n`);\n };\n\n super({\n transform(chunk, controller) {\n buffer += chunk;\n let index = buffer.indexOf(\"\\n\");\n while (index !== -1) {\n handleLine(buffer.slice(0, index), controller);\n buffer = buffer.slice(index + 1);\n index = buffer.indexOf(\"\\n\");\n }\n },\n flush(controller) {\n if (buffer.length > 0) handleLine(buffer, controller);\n flushPending(controller);\n },\n });\n }\n}\n\n// Wraps a base fetch (default: global) so SSE responses are piped through\n// SseNormalizeTransform; non-SSE responses (JSON errors, etc.) pass through\n// untouched. Used for the openai-responses protocol only — chat-completions\n// relays already emit one event per data line.\nexport function createSseNormalizingFetch(baseFetch: typeof fetch = fetch): typeof fetch {\n return async (input, init) => {\n const response = await baseFetch(input, init);\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (!response.body || !contentType.includes(\"text/event-stream\")) {\n return response;\n }\n const body = response.body\n .pipeThrough(new TextDecoderStream())\n .pipeThrough(new SseNormalizeTransform())\n .pipeThrough(new TextEncoderStream());\n // The rewritten body no longer matches the original bytes, so the old\n // content-length/content-encoding would lie about (and corrupt) it.\n const headers = new Headers(response.headers);\n headers.delete(\"content-length\");\n headers.delete(\"content-encoding\");\n return new Response(body, {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n };\n}\n"],"mappings":"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","names":[]}
|