dsh-subscriptions 0.1.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.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +129 -0
  3. package/cordis.patch.yml +12 -0
  4. package/lib/auth/claude-code-creds.d.ts +23 -0
  5. package/lib/auth/claude-code-creds.js +172 -0
  6. package/lib/auth/device-flow.d.ts +55 -0
  7. package/lib/auth/device-flow.js +177 -0
  8. package/lib/auth/jwt.d.ts +10 -0
  9. package/lib/auth/jwt.js +25 -0
  10. package/lib/auth/oauth-flow.d.ts +91 -0
  11. package/lib/auth/oauth-flow.js +227 -0
  12. package/lib/auth/pkce.d.ts +31 -0
  13. package/lib/auth/pkce.js +35 -0
  14. package/lib/auth/rpc.d.ts +209 -0
  15. package/lib/auth/rpc.js +386 -0
  16. package/lib/auth/store.d.ts +196 -0
  17. package/lib/auth/store.js +468 -0
  18. package/lib/client/CursorCard.d.ts +9 -0
  19. package/lib/client/CursorCard.js +113 -0
  20. package/lib/client/ExternalUsageCards.d.ts +9 -0
  21. package/lib/client/ExternalUsageCards.js +72 -0
  22. package/lib/client/ImageGallery.d.ts +54 -0
  23. package/lib/client/ImageGallery.js +112 -0
  24. package/lib/client/ImageGenerateToolview.d.ts +48 -0
  25. package/lib/client/ImageGenerateToolview.js +133 -0
  26. package/lib/client/ProviderAccountManager.d.ts +13 -0
  27. package/lib/client/ProviderAccountManager.js +147 -0
  28. package/lib/client/ProviderModelEditor.d.ts +39 -0
  29. package/lib/client/ProviderModelEditor.js +107 -0
  30. package/lib/client/SpeedSelect.d.ts +71 -0
  31. package/lib/client/SpeedSelect.js +177 -0
  32. package/lib/client/SubscriptionUsageBadge.d.ts +116 -0
  33. package/lib/client/SubscriptionUsageBadge.js +543 -0
  34. package/lib/client/SubscriptionsSection.d.ts +140 -0
  35. package/lib/client/SubscriptionsSection.js +544 -0
  36. package/lib/client/UsageBadgeDisplaySetting.d.ts +7 -0
  37. package/lib/client/UsageBadgeDisplaySetting.js +36 -0
  38. package/lib/client/VideoGenerateToolview.d.ts +42 -0
  39. package/lib/client/VideoGenerateToolview.js +175 -0
  40. package/lib/client/account-preferences.d.ts +24 -0
  41. package/lib/client/account-preferences.js +21 -0
  42. package/lib/client/fast-command.d.ts +2 -0
  43. package/lib/client/fast-command.js +4 -0
  44. package/lib/client/host-contract.d.ts +90 -0
  45. package/lib/client/host-contract.js +101 -0
  46. package/lib/client/host-icons.d.ts +24 -0
  47. package/lib/client/host-icons.js +21 -0
  48. package/lib/client/index.d.ts +27 -0
  49. package/lib/client/index.js +136 -0
  50. package/lib/client/locales.d.ts +297 -0
  51. package/lib/client/locales.js +295 -0
  52. package/lib/client/subscription-card-styles.d.ts +3 -0
  53. package/lib/client/subscription-card-styles.js +53 -0
  54. package/lib/client/subscriptions-rpc.d.ts +14 -0
  55. package/lib/client/subscriptions-rpc.js +34 -0
  56. package/lib/client/usage-badge-preferences.d.ts +11 -0
  57. package/lib/client/usage-badge-preferences.js +41 -0
  58. package/lib/client.js +4472 -0
  59. package/lib/client.js.map +1 -0
  60. package/lib/compat.d.ts +36 -0
  61. package/lib/compat.js +20 -0
  62. package/lib/http.d.ts +6 -0
  63. package/lib/http.js +15 -0
  64. package/lib/index.d.ts +80 -0
  65. package/lib/index.js +14018 -0
  66. package/lib/model-defaults.d.ts +23 -0
  67. package/lib/model-defaults.js +237 -0
  68. package/lib/provider-settings.d.ts +39 -0
  69. package/lib/provider-settings.js +151 -0
  70. package/lib/providers/account-preferences.d.ts +41 -0
  71. package/lib/providers/account-preferences.js +153 -0
  72. package/lib/providers/accounts.d.ts +112 -0
  73. package/lib/providers/accounts.js +142 -0
  74. package/lib/providers/antigravity-oauth-client.d.ts +7 -0
  75. package/lib/providers/antigravity-oauth-client.js +7 -0
  76. package/lib/providers/antigravity.d.ts +110 -0
  77. package/lib/providers/antigravity.js +516 -0
  78. package/lib/providers/catalog-store.d.ts +48 -0
  79. package/lib/providers/catalog-store.js +243 -0
  80. package/lib/providers/claude-cli-version.d.ts +15 -0
  81. package/lib/providers/claude-cli-version.js +24 -0
  82. package/lib/providers/claude.d.ts +181 -0
  83. package/lib/providers/claude.js +680 -0
  84. package/lib/providers/codex-client-version.d.ts +8 -0
  85. package/lib/providers/codex-client-version.js +17 -0
  86. package/lib/providers/codex-search.d.ts +29 -0
  87. package/lib/providers/codex-search.js +198 -0
  88. package/lib/providers/codex.d.ts +229 -0
  89. package/lib/providers/codex.js +821 -0
  90. package/lib/providers/common.d.ts +389 -0
  91. package/lib/providers/common.js +656 -0
  92. package/lib/providers/copilot.d.ts +344 -0
  93. package/lib/providers/copilot.js +888 -0
  94. package/lib/providers/cursor-adapter.d.ts +15 -0
  95. package/lib/providers/cursor-adapter.js +39 -0
  96. package/lib/providers/cursor-auth.d.ts +43 -0
  97. package/lib/providers/cursor-auth.js +169 -0
  98. package/lib/providers/cursor-model-settings.d.ts +15 -0
  99. package/lib/providers/cursor-model-settings.js +51 -0
  100. package/lib/providers/cursor-usage.d.ts +9 -0
  101. package/lib/providers/cursor-usage.js +112 -0
  102. package/lib/providers/external-usage-controller.d.ts +19 -0
  103. package/lib/providers/external-usage-controller.js +32 -0
  104. package/lib/providers/external-usage.d.ts +8 -0
  105. package/lib/providers/external-usage.js +82 -0
  106. package/lib/providers/grok.d.ts +205 -0
  107. package/lib/providers/grok.js +713 -0
  108. package/lib/providers/image-pool.d.ts +30 -0
  109. package/lib/providers/image-pool.js +89 -0
  110. package/lib/providers/npm-cli-version.d.ts +56 -0
  111. package/lib/providers/npm-cli-version.js +98 -0
  112. package/lib/providers/pool-family.d.ts +56 -0
  113. package/lib/providers/pool-family.js +45 -0
  114. package/lib/providers/pool-health.d.ts +74 -0
  115. package/lib/providers/pool-health.js +150 -0
  116. package/lib/providers/pool-usage.d.ts +86 -0
  117. package/lib/providers/pool-usage.js +223 -0
  118. package/lib/providers/pool.d.ts +108 -0
  119. package/lib/providers/pool.js +368 -0
  120. package/lib/providers/rate-limit.d.ts +192 -0
  121. package/lib/providers/rate-limit.js +338 -0
  122. package/lib/tools/image-generate.d.ts +128 -0
  123. package/lib/tools/image-generate.js +438 -0
  124. package/lib/tools/registration.d.ts +15 -0
  125. package/lib/tools/registration.js +24 -0
  126. package/lib/tools/video-generate.d.ts +89 -0
  127. package/lib/tools/video-generate.js +257 -0
  128. package/lib/tools/x-search.d.ts +58 -0
  129. package/lib/tools/x-search.js +197 -0
  130. package/lib/translate/anthropic.d.ts +161 -0
  131. package/lib/translate/anthropic.js +515 -0
  132. package/lib/translate/antigravity-schema.d.ts +4 -0
  133. package/lib/translate/antigravity-schema.js +145 -0
  134. package/lib/translate/antigravity-thinking.d.ts +5 -0
  135. package/lib/translate/antigravity-thinking.js +44 -0
  136. package/lib/translate/antigravity.d.ts +114 -0
  137. package/lib/translate/antigravity.js +366 -0
  138. package/lib/translate/chat-completions.d.ts +120 -0
  139. package/lib/translate/chat-completions.js +382 -0
  140. package/lib/translate/resolved.d.ts +80 -0
  141. package/lib/translate/resolved.js +118 -0
  142. package/lib/translate/responses.d.ts +179 -0
  143. package/lib/translate/responses.js +408 -0
  144. package/lib/translate/sse.d.ts +21 -0
  145. package/lib/translate/sse.js +56 -0
  146. package/package.json +114 -0
  147. package/vendor/cursor/LICENSE +21 -0
  148. package/vendor/cursor/index.d.ts +18 -0
  149. package/vendor/cursor/index.js +3574 -0
  150. package/vendor/cursor/proto.js +254 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Translate between the harness message vocabulary and the OpenAI Responses
3
+ * API wire format shared by the codex and grok providers: request input
4
+ * assembly, tool schema mapping, and a push-model SSE-event → StreamChunk
5
+ * state machine ({@link ResponsesStreamTranslator}) so tests need no streams.
6
+ */
7
+ import { LlmError } from '@deepseek-ai/dsh-llm';
8
+ import type { StreamChunk, TokenUsage, ToolSchema } from '@deepseek-ai/dsh-llm';
9
+ import type { TranslatableMessage } from './resolved.js';
10
+ /** Assembled `instructions` + `input` pair for one Responses request. */
11
+ export interface ResponsesRequestInput {
12
+ /** System text for the top-level `instructions` field; absent when there is none. */
13
+ instructions?: string;
14
+ /** Responses `input` items in conversation order. */
15
+ input: Record<string, unknown>[];
16
+ }
17
+ /**
18
+ * One COMPLETED reasoning output item captured off a response, replayed as
19
+ * the complete item on a later request of the same conversation. The
20
+ * Responses input schema does not treat a reasoning item's `id` or
21
+ * `summary` as optional — a bare `{ type, encrypted_content }` is not a
22
+ * valid input item — so the capture keeps the item's gateway id (as it
23
+ * arrived on the done event), its summary parts, its status, and the
24
+ * encrypted payload. `encrypted_content` is the only required field here:
25
+ * items without one are simply never captured.
26
+ */
27
+ export interface ReasoningReplayItem {
28
+ type: 'reasoning';
29
+ /** The item's gateway id as it arrived on the done event. */
30
+ id?: string;
31
+ /** Summary parts, passed through when the gateway disclosed them. */
32
+ summary?: unknown[];
33
+ /** Item lifecycle status, passed through when present (typically `completed`). */
34
+ status?: string;
35
+ /** Encrypted reasoning payload; the reason the item is worth replaying. */
36
+ encrypted_content: string;
37
+ }
38
+ /**
39
+ * Convert harness messages into Responses `instructions` + `input` items.
40
+ * System-role messages become `instructions`; an explicit `system` argument
41
+ * wins over them when both exist. Reasoning blocks are never replayed in
42
+ * their text form: a Responses model continuing past a tool call needs its
43
+ * reasoning back as the provider's completed reasoning items (id, summary,
44
+ * and the ENCRYPTED payload), so `reasoningFor` may resolve per-call
45
+ * captured items, replayed ahead of the matching function_call item. Images
46
+ * must arrive pre-resolved
47
+ * ({@link TranslatableMessage}); an unresolved ImageBlock is skipped because
48
+ * its bytes are unreachable here.
49
+ * @param messages - ordered conversation messages with resolved images.
50
+ * @param system - explicit system prompt, which takes precedence.
51
+ * @param reasoningFor - resolves one tool call id to the COMPLETED reasoning
52
+ * items captured for it (id, summary, status, encrypted payload), replayed
53
+ * ahead of the matching function_call item, when the adapter kept them.
54
+ * @returns request fields ready to merge into the request body.
55
+ */
56
+ export declare function toResponsesInput(messages: readonly TranslatableMessage[], system?: string, reasoningFor?: (callId: string) => readonly ReasoningReplayItem[] | undefined): ResponsesRequestInput;
57
+ /**
58
+ * Map harness tool schemas to Responses function tools.
59
+ *
60
+ * OpenAI Responses may normalize schemas into strict mode when `strict` is
61
+ * omitted. Codex and Copilot opt out to preserve harness optional parameters;
62
+ * this does not prevent a model from voluntarily supplying optional fields.
63
+ * Other providers keep their existing defaults unless explicitly opted out.
64
+ * @param tools - tool schemas from the request.
65
+ * @param options - provider-specific opt-out from strict schema normalization.
66
+ * @returns Responses `tools` array entries.
67
+ */
68
+ export declare function toResponsesTools(tools: readonly ToolSchema[], options?: {
69
+ strict?: false;
70
+ }): Record<string, unknown>[];
71
+ /** The subset of Responses SSE event shapes this translator reads. */
72
+ export interface ResponsesStreamEvent {
73
+ type: string;
74
+ item_id?: string;
75
+ /**
76
+ * Position of the event's item in the response's output array. The spec
77
+ * carries it on output_item/delta events; Copilot's adapter uses it as the
78
+ * stable item correlator when the gateway mints fresh ids per event.
79
+ */
80
+ output_index?: number;
81
+ content_index?: number;
82
+ summary_index?: number;
83
+ delta?: string;
84
+ item?: {
85
+ type?: string;
86
+ id?: string;
87
+ call_id?: string;
88
+ name?: string;
89
+ arguments?: string;
90
+ /** Encrypted reasoning payload, present when the request asked to include it. */
91
+ encrypted_content?: string;
92
+ /** Summary parts of a completed reasoning item, when the gateway disclosed them. */
93
+ summary?: unknown[];
94
+ /** Item lifecycle status (e.g. `completed`), when present. */
95
+ status?: string;
96
+ content?: Array<{
97
+ type?: string;
98
+ text?: string;
99
+ }>;
100
+ };
101
+ response?: {
102
+ status?: string;
103
+ usage?: ResponsesUsage;
104
+ error?: {
105
+ code?: string;
106
+ message?: string;
107
+ };
108
+ incomplete_details?: {
109
+ reason?: string;
110
+ };
111
+ };
112
+ code?: string;
113
+ message?: string;
114
+ }
115
+ /** Responses `usage` object shape. */
116
+ export interface ResponsesUsage {
117
+ input_tokens: number;
118
+ output_tokens: number;
119
+ input_tokens_details?: {
120
+ cached_tokens?: number;
121
+ };
122
+ output_tokens_details?: {
123
+ reasoning_tokens?: number;
124
+ };
125
+ }
126
+ /**
127
+ * Map Responses usage to disjoint harness counts (cached input is subtracted
128
+ * out of `inputTokens` and reported as `cacheReadTokens`).
129
+ * @param usage - wire usage from `response.completed`.
130
+ * @returns harness token usage.
131
+ */
132
+ export declare function mapResponsesUsage(usage: ResponsesUsage): TokenUsage;
133
+ /**
134
+ * Classify a Responses failure payload into a thrown LlmError.
135
+ * @param code - provider error code, when present.
136
+ * @param message - provider error message, when present.
137
+ * @returns the mapped error (context overflow, quota, otherwise SERVER).
138
+ */
139
+ export declare function responsesFailure(code: string | undefined, message: string | undefined): LlmError;
140
+ /**
141
+ * Push-model Responses SSE translator: feed each parsed event object to
142
+ * {@link push} and collect the emitted harness StreamChunks. Block indexes
143
+ * are allocated in first-seen order; `usage` is emitted before the terminal
144
+ * `finish`, and nothing is emitted after it. Terminal provider failures
145
+ * throw {@link LlmError}.
146
+ */
147
+ export declare class ResponsesStreamTranslator {
148
+ private blocks;
149
+ private order;
150
+ private nextIndex;
151
+ private sawToolCall;
152
+ /** Set once `response.completed` produced the terminal finish chunk. */
153
+ terminated: boolean;
154
+ private open;
155
+ private textBlock;
156
+ private reasoningBlock;
157
+ private close;
158
+ /** Close every still-open block for one output item (prefix match on the key). */
159
+ private closeItem;
160
+ /** Close every still-open block (provider ended the response without done events). */
161
+ private closeAll;
162
+ private closeKeyIfOpen;
163
+ /**
164
+ * Process one parsed Responses SSE event.
165
+ * @param event - the parsed event object.
166
+ * @returns the StreamChunks this event produced (possibly none).
167
+ */
168
+ push(event: ResponsesStreamEvent): StreamChunk[];
169
+ }
170
+ /**
171
+ * Consume a Responses SSE byte stream and yield harness StreamChunks.
172
+ * @param stream - raw response body.
173
+ * @param onActivity - transport-activity callback for the idle watchdog.
174
+ * @param transform - optional per-event rewrite applied before translation
175
+ * (Copilot's gateway mints a fresh item id per event; the adapter rewrites
176
+ * them into stable per-item keys).
177
+ * @returns the chunk stream; throws when the stream ends before `response.completed`.
178
+ */
179
+ export declare function streamResponses(stream: ReadableStream<Uint8Array>, onActivity?: () => void, transform?: (event: ResponsesStreamEvent) => ResponsesStreamEvent): AsyncGenerator<StreamChunk>;
@@ -0,0 +1,408 @@
1
+ /**
2
+ * Translate between the harness message vocabulary and the OpenAI Responses
3
+ * API wire format shared by the codex and grok providers: request input
4
+ * assembly, tool schema mapping, and a push-model SSE-event → StreamChunk
5
+ * state machine ({@link ResponsesStreamTranslator}) so tests need no streams.
6
+ */
7
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, isContextWindowExceededError, isQuotaExceededError, LlmError, QUOTA_EXCEEDED_CODE, } from '@deepseek-ai/dsh-llm';
8
+ import { ToolCallId } from '../compat.js';
9
+ import { parseSse } from './sse.js';
10
+ import { withToolResultImages } from './resolved.js';
11
+ /** Flatten a tool result's content to plain text for `function_call_output`. */
12
+ function toolResultText(block) {
13
+ return block.content.map(part => (part.type === 'text' ? part.text : '')).join('');
14
+ }
15
+ /**
16
+ * Convert harness messages into Responses `instructions` + `input` items.
17
+ * System-role messages become `instructions`; an explicit `system` argument
18
+ * wins over them when both exist. Reasoning blocks are never replayed in
19
+ * their text form: a Responses model continuing past a tool call needs its
20
+ * reasoning back as the provider's completed reasoning items (id, summary,
21
+ * and the ENCRYPTED payload), so `reasoningFor` may resolve per-call
22
+ * captured items, replayed ahead of the matching function_call item. Images
23
+ * must arrive pre-resolved
24
+ * ({@link TranslatableMessage}); an unresolved ImageBlock is skipped because
25
+ * its bytes are unreachable here.
26
+ * @param messages - ordered conversation messages with resolved images.
27
+ * @param system - explicit system prompt, which takes precedence.
28
+ * @param reasoningFor - resolves one tool call id to the COMPLETED reasoning
29
+ * items captured for it (id, summary, status, encrypted payload), replayed
30
+ * ahead of the matching function_call item, when the adapter kept them.
31
+ * @returns request fields ready to merge into the request body.
32
+ */
33
+ export function toResponsesInput(messages, system, reasoningFor) {
34
+ const input = [];
35
+ const systemTexts = [];
36
+ // [2026-08-23]-[reasoning models lose their chain of thought across a tool
37
+ // round trip unless the completed reasoning items ride back in; dedupe by
38
+ // ARRAY REFERENCE so parallel calls of one response (which share one array
39
+ // instance) replay the items once, before the first of them]
40
+ let lastReplay;
41
+ for (const message of withToolResultImages(messages)) {
42
+ if (message.role === 'system') {
43
+ for (const block of message.content) {
44
+ if (block.type === 'text')
45
+ systemTexts.push(block.text);
46
+ }
47
+ continue;
48
+ }
49
+ // Current harness tool results are first-class role=tool messages.
50
+ // Responses represents their text as function_call_output items.
51
+ if (message.role === 'tool') {
52
+ const callId = message.toolCallId ?? message.tool_call_id
53
+ ?? (message.source?.kind === 'tool' ? String(message.source.callId) : undefined);
54
+ if (callId === undefined)
55
+ throw new LlmError('tool result has no call id', 'INVALID_REQUEST');
56
+ input.push({
57
+ type: 'function_call_output',
58
+ call_id: callId,
59
+ output: message.content.map(block => block.type === 'text' ? block.text : '').join(''),
60
+ });
61
+ continue;
62
+ }
63
+ const role = message.role;
64
+ let content = [];
65
+ const flushMessage = () => {
66
+ if (content.length === 0)
67
+ return;
68
+ input.push({ type: 'message', role, content });
69
+ content = [];
70
+ };
71
+ for (const block of message.content) {
72
+ switch (block.type) {
73
+ case 'text':
74
+ content.push({ type: role === 'assistant' ? 'output_text' : 'input_text', text: block.text });
75
+ break;
76
+ case 'tool-call': {
77
+ flushMessage();
78
+ const encrypted = reasoningFor?.(String(block.id));
79
+ if (encrypted !== undefined && encrypted !== lastReplay) {
80
+ for (const item of encrypted) {
81
+ input.push({
82
+ type: 'reasoning',
83
+ ...item.id === undefined ? {} : { id: item.id },
84
+ ...item.summary === undefined ? {} : { summary: item.summary },
85
+ ...item.status === undefined ? {} : { status: item.status },
86
+ encrypted_content: item.encrypted_content,
87
+ });
88
+ }
89
+ lastReplay = encrypted;
90
+ }
91
+ input.push({
92
+ type: 'function_call',
93
+ call_id: String(block.id),
94
+ name: block.name,
95
+ arguments: block.arguments,
96
+ });
97
+ break;
98
+ }
99
+ case 'tool-result':
100
+ flushMessage();
101
+ input.push({
102
+ type: 'function_call_output',
103
+ call_id: String(block.toolCallId),
104
+ output: toolResultText(block),
105
+ });
106
+ break;
107
+ case 'image':
108
+ if ('dataBase64' in block) {
109
+ content.push({
110
+ type: 'input_image',
111
+ image_url: `data:${block.mediaType};base64,${block.dataBase64}`,
112
+ });
113
+ }
114
+ // An unresolved ImageBlock carries only an attachment reference; the
115
+ // adapter resolves images before translation, so this is skipped.
116
+ break;
117
+ default:
118
+ // reasoning's text form is not replayed (encrypted replay rides
119
+ // reasoningFor), unknown blocks.
120
+ break;
121
+ }
122
+ }
123
+ flushMessage();
124
+ }
125
+ const instructions = system ?? (systemTexts.length > 0 ? systemTexts.join('\n\n') : undefined);
126
+ return { ...instructions === undefined ? {} : { instructions }, input };
127
+ }
128
+ /**
129
+ * Map harness tool schemas to Responses function tools.
130
+ *
131
+ * OpenAI Responses may normalize schemas into strict mode when `strict` is
132
+ * omitted. Codex and Copilot opt out to preserve harness optional parameters;
133
+ * this does not prevent a model from voluntarily supplying optional fields.
134
+ * Other providers keep their existing defaults unless explicitly opted out.
135
+ * @param tools - tool schemas from the request.
136
+ * @param options - provider-specific opt-out from strict schema normalization.
137
+ * @returns Responses `tools` array entries.
138
+ */
139
+ export function toResponsesTools(tools, options = {}) {
140
+ return tools.map(tool => ({
141
+ type: 'function',
142
+ name: tool.name,
143
+ description: tool.description,
144
+ parameters: tool.parameters,
145
+ ...options.strict === undefined ? {} : { strict: options.strict },
146
+ }));
147
+ }
148
+ /**
149
+ * Map Responses usage to disjoint harness counts (cached input is subtracted
150
+ * out of `inputTokens` and reported as `cacheReadTokens`).
151
+ * @param usage - wire usage from `response.completed`.
152
+ * @returns harness token usage.
153
+ */
154
+ export function mapResponsesUsage(usage) {
155
+ const cached = usage.input_tokens_details?.cached_tokens;
156
+ const reasoning = usage.output_tokens_details?.reasoning_tokens;
157
+ return {
158
+ inputTokens: usage.input_tokens - (cached ?? 0),
159
+ outputTokens: usage.output_tokens,
160
+ ...cached !== undefined ? { cacheReadTokens: cached } : {},
161
+ ...reasoning !== undefined ? { reasoningTokens: reasoning } : {},
162
+ };
163
+ }
164
+ /**
165
+ * Classify a Responses failure payload into a thrown LlmError.
166
+ * @param code - provider error code, when present.
167
+ * @param message - provider error message, when present.
168
+ * @returns the mapped error (context overflow, quota, otherwise SERVER).
169
+ */
170
+ export function responsesFailure(code, message) {
171
+ const text = message ?? code ?? 'the provider reported a failed response';
172
+ const detail = `${code ?? ''} ${message ?? ''}`;
173
+ if (code === 'context_window_exceeded' || isContextWindowExceededError(detail)) {
174
+ return new LlmError(text, CONTEXT_WINDOW_EXCEEDED_CODE);
175
+ }
176
+ if ((code !== undefined && /insufficient|quota/i.test(code)) || isQuotaExceededError(detail)) {
177
+ return new LlmError(text, QUOTA_EXCEEDED_CODE);
178
+ }
179
+ return new LlmError(text, 'SERVER');
180
+ }
181
+ /** Assemble the final ContentBlock for one open block. */
182
+ function closeBlock(block) {
183
+ switch (block.kind) {
184
+ case 'text':
185
+ return { type: 'text', text: block.text };
186
+ case 'reasoning':
187
+ return { type: 'reasoning', text: block.text };
188
+ case 'tool-call':
189
+ return {
190
+ type: 'tool-call',
191
+ id: ToolCallId(block.callId),
192
+ name: block.name ?? '',
193
+ arguments: block.text,
194
+ };
195
+ }
196
+ }
197
+ /**
198
+ * Push-model Responses SSE translator: feed each parsed event object to
199
+ * {@link push} and collect the emitted harness StreamChunks. Block indexes
200
+ * are allocated in first-seen order; `usage` is emitted before the terminal
201
+ * `finish`, and nothing is emitted after it. Terminal provider failures
202
+ * throw {@link LlmError}.
203
+ */
204
+ export class ResponsesStreamTranslator {
205
+ blocks = new Map();
206
+ order = [];
207
+ nextIndex = 0;
208
+ sawToolCall = false;
209
+ /** Set once `response.completed` produced the terminal finish chunk. */
210
+ terminated = false;
211
+ open(key, kind, chunks, callId = '', name) {
212
+ const block = {
213
+ index: this.nextIndex++,
214
+ kind,
215
+ text: '',
216
+ callId,
217
+ ...name === undefined ? {} : { name },
218
+ };
219
+ this.blocks.set(key, block);
220
+ this.order.push(block);
221
+ chunks.push({ type: 'block-start', index: block.index, blockType: kind });
222
+ return block;
223
+ }
224
+ textBlock(key, chunks) {
225
+ return this.blocks.get(key) ?? this.open(key, 'text', chunks);
226
+ }
227
+ reasoningBlock(key, chunks) {
228
+ return this.blocks.get(key) ?? this.open(key, 'reasoning', chunks);
229
+ }
230
+ close(key, chunks) {
231
+ const block = this.blocks.get(key);
232
+ if (block === undefined)
233
+ return;
234
+ this.blocks.delete(key);
235
+ chunks.push({ type: 'block-end', index: block.index, block: closeBlock(block) });
236
+ }
237
+ /** Close every still-open block for one output item (prefix match on the key). */
238
+ closeItem(itemId, chunks) {
239
+ for (const key of [...this.blocks.keys()]) {
240
+ if (key.startsWith(`${itemId}:`))
241
+ this.close(key, chunks);
242
+ }
243
+ }
244
+ /** Close every still-open block (provider ended the response without done events). */
245
+ closeAll(chunks) {
246
+ for (const block of this.order)
247
+ this.closeKeyIfOpen(block, chunks);
248
+ }
249
+ closeKeyIfOpen(block, chunks) {
250
+ for (const [key, candidate] of this.blocks) {
251
+ if (candidate === block) {
252
+ this.blocks.delete(key);
253
+ chunks.push({ type: 'block-end', index: block.index, block: closeBlock(block) });
254
+ return;
255
+ }
256
+ }
257
+ }
258
+ /**
259
+ * Process one parsed Responses SSE event.
260
+ * @param event - the parsed event object.
261
+ * @returns the StreamChunks this event produced (possibly none).
262
+ */
263
+ push(event) {
264
+ if (this.terminated)
265
+ return [];
266
+ const chunks = [];
267
+ switch (event.type) {
268
+ case 'response.output_item.added': {
269
+ const item = event.item;
270
+ if (item?.type === 'function_call' && item.id !== undefined) {
271
+ this.sawToolCall = true;
272
+ const callId = item.call_id ?? '';
273
+ const block = this.open(`${item.id}:call`, 'tool-call', chunks, callId, item.name);
274
+ chunks.push({
275
+ type: 'tool-call-delta',
276
+ index: block.index,
277
+ id: ToolCallId(callId),
278
+ ...item.name === undefined ? {} : { name: item.name },
279
+ argumentsDelta: '',
280
+ });
281
+ }
282
+ return chunks;
283
+ }
284
+ case 'response.output_text.delta': {
285
+ const key = `${event.item_id ?? ''}:text:${String(event.content_index ?? 0)}`;
286
+ const block = this.textBlock(key, chunks);
287
+ block.text += event.delta ?? '';
288
+ chunks.push({ type: 'text-delta', index: block.index, text: event.delta ?? '' });
289
+ return chunks;
290
+ }
291
+ case 'response.reasoning_summary_text.delta':
292
+ case 'response.reasoning_text.delta': {
293
+ const sub = event.summary_index ?? event.content_index ?? 0;
294
+ const key = `${event.item_id ?? ''}:reason:${String(sub)}`;
295
+ const block = this.reasoningBlock(key, chunks);
296
+ block.text += event.delta ?? '';
297
+ chunks.push({ type: 'reasoning-delta', index: block.index, text: event.delta ?? '' });
298
+ return chunks;
299
+ }
300
+ case 'response.function_call_arguments.delta': {
301
+ const key = `${event.item_id ?? ''}:call`;
302
+ let block = this.blocks.get(key);
303
+ if (block === undefined) {
304
+ // The item.added event was missed; open the block from the delta alone.
305
+ this.sawToolCall = true;
306
+ block = this.open(key, 'tool-call', chunks);
307
+ }
308
+ block.text += event.delta ?? '';
309
+ chunks.push({
310
+ type: 'tool-call-delta',
311
+ index: block.index,
312
+ id: ToolCallId(block.callId),
313
+ ...block.name === undefined ? {} : { name: block.name },
314
+ argumentsDelta: event.delta ?? '',
315
+ });
316
+ return chunks;
317
+ }
318
+ case 'response.output_item.done': {
319
+ const item = event.item;
320
+ if (item === undefined || item.id === undefined)
321
+ return chunks;
322
+ if (item.type === 'function_call') {
323
+ const key = `${item.id}:call`;
324
+ // The provider may deliver the complete arguments only on done.
325
+ const block = this.blocks.get(key);
326
+ if (block !== undefined && block.text.length === 0 && item.arguments !== undefined) {
327
+ block.text = item.arguments;
328
+ }
329
+ this.close(key, chunks);
330
+ }
331
+ else if (item.type === 'message') {
332
+ if (![...this.blocks.keys()].some(key => key.startsWith(`${item.id}:text:`))) {
333
+ // No deltas arrived for this item; synthesize blocks from the done payload.
334
+ for (const [partIndex, part] of (item.content ?? []).entries()) {
335
+ if (part?.type !== 'output_text' || typeof part.text !== 'string' || part.text.length === 0)
336
+ continue;
337
+ const block = this.open(`${item.id}:text:${partIndex}`, 'text', chunks);
338
+ block.text = part.text;
339
+ this.close(`${item.id}:text:${partIndex}`, chunks);
340
+ }
341
+ }
342
+ this.closeItem(item.id, chunks);
343
+ }
344
+ else {
345
+ this.closeItem(item.id, chunks);
346
+ }
347
+ return chunks;
348
+ }
349
+ case 'response.completed': {
350
+ this.terminated = true;
351
+ this.closeAll(chunks);
352
+ const usage = event.response?.usage;
353
+ if (usage !== undefined)
354
+ chunks.push({ type: 'usage', usage: mapResponsesUsage(usage) });
355
+ if (this.order.length === 0) {
356
+ chunks.push({
357
+ type: 'finish',
358
+ reason: {
359
+ kind: 'error',
360
+ failure: { message: 'model returned a completed response with no content', code: EMPTY_RESPONSE_CODE },
361
+ },
362
+ });
363
+ }
364
+ else {
365
+ chunks.push({ type: 'finish', reason: { kind: this.sawToolCall ? 'tool-calls' : 'stop' } });
366
+ }
367
+ return chunks;
368
+ }
369
+ case 'response.failed':
370
+ throw responsesFailure(event.response?.error?.code, event.response?.error?.message);
371
+ case 'response.incomplete':
372
+ throw responsesFailure(event.response?.incomplete_details?.reason, event.response?.error?.message
373
+ ?? `the provider reported an incomplete response (${event.response?.incomplete_details?.reason ?? 'unknown reason'})`);
374
+ case 'error':
375
+ throw responsesFailure(event.code, event.message);
376
+ default:
377
+ // response.created, response.in_progress, content_part events, etc.: no chunks.
378
+ return chunks;
379
+ }
380
+ }
381
+ }
382
+ /**
383
+ * Consume a Responses SSE byte stream and yield harness StreamChunks.
384
+ * @param stream - raw response body.
385
+ * @param onActivity - transport-activity callback for the idle watchdog.
386
+ * @param transform - optional per-event rewrite applied before translation
387
+ * (Copilot's gateway mints a fresh item id per event; the adapter rewrites
388
+ * them into stable per-item keys).
389
+ * @returns the chunk stream; throws when the stream ends before `response.completed`.
390
+ */
391
+ export async function* streamResponses(stream, onActivity, transform) {
392
+ const translator = new ResponsesStreamTranslator();
393
+ for await (const sseEvent of parseSse(stream, onActivity)) {
394
+ let event;
395
+ try {
396
+ event = JSON.parse(sseEvent.data);
397
+ }
398
+ catch {
399
+ throw new LlmError(`malformed SSE payload: ${sseEvent.data.slice(0, 120)}`, 'MALFORMED_RESPONSE');
400
+ }
401
+ if (transform !== undefined)
402
+ event = transform(event);
403
+ yield* translator.push(event);
404
+ if (translator.terminated)
405
+ return;
406
+ }
407
+ throw new LlmError('Responses SSE stream ended before response.completed', 'STREAM_CLOSED');
408
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Minimal SSE byte-stream parser (~30 lines of framing): reassembles chunks,
3
+ * splits CRLF/LF lines, joins multi-`data:` payloads, skips comments and
4
+ * non-data fields, and dispatches an event only on its blank-line terminator.
5
+ * An unterminated tail at EOF is truncation and is dropped, matching the
6
+ * spec-strict framing the harness's own adapters use.
7
+ */
8
+ /** One parsed SSE event. */
9
+ export interface SseEvent {
10
+ /** Joined `data:` lines of the event. */
11
+ data: string;
12
+ /** The `event:` field, when present. */
13
+ event?: string;
14
+ }
15
+ /**
16
+ * Decode an SSE byte stream into events.
17
+ * @param stream - raw response bytes; reads may split anywhere, including mid-UTF-8 sequence.
18
+ * @param onActivity - called on every received chunk and comment line; drives the idle watchdog.
19
+ * @returns events in arrival order.
20
+ */
21
+ export declare function parseSse(stream: ReadableStream<Uint8Array>, onActivity?: () => void): AsyncGenerator<SseEvent>;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Minimal SSE byte-stream parser (~30 lines of framing): reassembles chunks,
3
+ * splits CRLF/LF lines, joins multi-`data:` payloads, skips comments and
4
+ * non-data fields, and dispatches an event only on its blank-line terminator.
5
+ * An unterminated tail at EOF is truncation and is dropped, matching the
6
+ * spec-strict framing the harness's own adapters use.
7
+ */
8
+ /**
9
+ * Decode an SSE byte stream into events.
10
+ * @param stream - raw response bytes; reads may split anywhere, including mid-UTF-8 sequence.
11
+ * @param onActivity - called on every received chunk and comment line; drives the idle watchdog.
12
+ * @returns events in arrival order.
13
+ */
14
+ export async function* parseSse(stream, onActivity) {
15
+ const reader = stream.getReader();
16
+ const decoder = new TextDecoder();
17
+ let pending = '';
18
+ let dataLines = [];
19
+ let eventName;
20
+ try {
21
+ while (true) {
22
+ const { done, value } = await reader.read();
23
+ if (done)
24
+ return;
25
+ onActivity?.();
26
+ pending += decoder.decode(value, { stream: true });
27
+ let newline = pending.indexOf('\n');
28
+ while (newline >= 0) {
29
+ let line = pending.slice(0, newline);
30
+ pending = pending.slice(newline + 1);
31
+ newline = pending.indexOf('\n');
32
+ if (line.endsWith('\r'))
33
+ line = line.slice(0, -1);
34
+ if (line.length === 0) {
35
+ if (dataLines.length > 0) {
36
+ yield { data: dataLines.join('\n'), ...eventName === undefined ? {} : { event: eventName } };
37
+ }
38
+ dataLines = [];
39
+ eventName = undefined;
40
+ }
41
+ else if (line.startsWith(':')) {
42
+ onActivity?.();
43
+ }
44
+ else if (line.startsWith('data:')) {
45
+ dataLines.push(line.slice(5).replace(/^ /, ''));
46
+ }
47
+ else if (line.startsWith('event:')) {
48
+ eventName = line.slice(6).replace(/^ /, '');
49
+ }
50
+ }
51
+ }
52
+ }
53
+ finally {
54
+ reader.releaseLock();
55
+ }
56
+ }