@agnt-sdk/studio 0.0.34 → 0.0.36

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 (40) hide show
  1. package/dist/BaseExecutor.d.ts +4 -1
  2. package/dist/BaseExecutor.d.ts.map +1 -1
  3. package/dist/BaseExecutor.js +18 -1
  4. package/dist/BaseExecutor.js.map +1 -1
  5. package/dist/__tests__/_streamMocks.d.ts +35 -0
  6. package/dist/__tests__/_streamMocks.d.ts.map +1 -0
  7. package/dist/__tests__/_streamMocks.js +102 -0
  8. package/dist/__tests__/_streamMocks.js.map +1 -0
  9. package/dist/executorFactory.d.ts +9 -1
  10. package/dist/executorFactory.d.ts.map +1 -1
  11. package/dist/executorFactory.js +28 -8
  12. package/dist/executorFactory.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/providers/anthropic.d.ts.map +1 -1
  18. package/dist/providers/anthropic.js +25 -7
  19. package/dist/providers/anthropic.js.map +1 -1
  20. package/dist/providers/deepseek.d.ts +7 -18
  21. package/dist/providers/deepseek.d.ts.map +1 -1
  22. package/dist/providers/deepseek.js +7 -197
  23. package/dist/providers/deepseek.js.map +1 -1
  24. package/dist/providers/google.d.ts.map +1 -1
  25. package/dist/providers/google.js +33 -6
  26. package/dist/providers/google.js.map +1 -1
  27. package/dist/providers/openai.d.ts.map +1 -1
  28. package/dist/providers/openai.js +29 -8
  29. package/dist/providers/openai.js.map +1 -1
  30. package/dist/providers/openaiCompatible.d.ts +47 -0
  31. package/dist/providers/openaiCompatible.d.ts.map +1 -0
  32. package/dist/providers/openaiCompatible.js +241 -0
  33. package/dist/providers/openaiCompatible.js.map +1 -0
  34. package/dist/providers/streaming.d.ts +109 -0
  35. package/dist/providers/streaming.d.ts.map +1 -0
  36. package/dist/providers/streaming.js +226 -0
  37. package/dist/providers/streaming.js.map +1 -0
  38. package/dist/types.d.ts +15 -4
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,241 @@
1
+ /**
2
+ * OpenAICompatibleExecutor — shared adapter for OpenAI-compatible providers
3
+ *
4
+ * One adapter for every provider that speaks the OpenAI Chat Completions wire
5
+ * format: Together AI (Kimi / Qwen), Fireworks, DeepInfra, DeepSeek, and any
6
+ * future open-model host. Per-provider differences — base URL, credentials,
7
+ * model IDs, pricing, cache behavior — live in CONFIG, never in code:
8
+ *
9
+ * - baseURL: resolved from OPENAI_COMPATIBLE_BASE_URLS[provider], overridable
10
+ * by credentials[provider].baseURL or model metadata.baseURL.
11
+ * - apiKey: credentials[provider].apiKey (via the existing secret manager;
12
+ * never stored in manifest/config rows).
13
+ * - model: manifest.spec.models[].model.
14
+ * - params: manifest.spec.models[].metadata (temperature, top_p, …).
15
+ *
16
+ * Adding a new OpenAI-compatible provider is therefore a config + credentials
17
+ * change (new AiModel row + creds entry), with no new adapter code required —
18
+ * unless it needs a base URL we don't know, which is a one-line registry entry.
19
+ *
20
+ * CACHING: these providers do AUTOMATIC (best-effort) prefix caching — the
21
+ * provider caches transparently, there is no billed cache write, and cached
22
+ * reads are reported back in usage.prompt_tokens_details.cached_tokens. We map
23
+ * that to cache_read_input_tokens and leave cache_creation_input_tokens at 0
24
+ * (there is no write concept here; the backend renders it as "—" / null based
25
+ * on the model's cacheMode). We never send Anthropic-style cache_control blocks
26
+ * — they are not part of the OpenAI wire format and must not leak through.
27
+ */
28
+ import OpenAI from 'openai';
29
+ import BaseExecutor from '../BaseExecutor.js';
30
+ import { streamWithRetry, consumeOpenAIStream, STREAM_ABSOLUTE_BACKSTOP_MS } from './streaming.js';
31
+ /** Known OpenAI-compatible provider base URLs. A provider not listed here is
32
+ * still usable by supplying baseURL via credentials or model metadata. */
33
+ export const OPENAI_COMPATIBLE_BASE_URLS = {
34
+ together: 'https://api.together.ai/v1',
35
+ fireworks: 'https://api.fireworks.ai/inference/v1',
36
+ deepinfra: 'https://api.deepinfra.com/v1/openai',
37
+ deepseek: 'https://api.deepseek.com/v1',
38
+ };
39
+ /** Providers routed through this adapter. Keep in sync with executorFactory. */
40
+ export const OPENAI_COMPATIBLE_PROVIDERS = new Set(Object.keys(OPENAI_COMPATIBLE_BASE_URLS));
41
+ export default class OpenAICompatibleExecutor extends BaseExecutor {
42
+ client;
43
+ providerName;
44
+ constructor(config) {
45
+ super(config);
46
+ // this.provider is set by BaseExecutor from the selected model config.
47
+ this.providerName = (this.provider || '').toLowerCase();
48
+ // Credentials are keyed by provider name. Real provider strings are
49
+ // lowercase ('together', 'deepseek'), but tolerate the original casing too.
50
+ const credMap = this.credentials;
51
+ const creds = credMap[this.provider] ?? credMap[this.providerName];
52
+ if (!creds) {
53
+ throw new Error(`[OpenAICompatibleExecutor] credentials.${this.providerName} is required`);
54
+ }
55
+ if (!creds.apiKey) {
56
+ throw new Error(`[OpenAICompatibleExecutor] credentials.${this.providerName}.apiKey is required`);
57
+ }
58
+ // baseURL resolution order: creds override → model metadata → registry.
59
+ const metadata = (this.primaryModelConfig.metadata || {});
60
+ const baseURL = creds.baseURL ||
61
+ metadata.baseURL ||
62
+ OPENAI_COMPATIBLE_BASE_URLS[this.providerName];
63
+ if (!baseURL) {
64
+ throw new Error(`[OpenAICompatibleExecutor] no baseURL for provider "${this.providerName}" — ` +
65
+ `add one to OPENAI_COMPATIBLE_BASE_URLS, credentials.${this.providerName}.baseURL, or model metadata.baseURL`);
66
+ }
67
+ // Initialize OpenAI-compatible client. invoke() STREAMS and bounds the
68
+ // response with an inter-chunk IDLE timeout (see streaming.ts), so a
69
+ // long-but-progressing turn — exactly Kimi doing multi-tool agentic work —
70
+ // never races a total-completion timeout. The client `timeout` here is only
71
+ // the SDK's own absolute request cap; set it to the streaming backstop and
72
+ // let the idle timeout be the operative ceiling. maxRetries matches the
73
+ // other streamed adapters.
74
+ this.client = new OpenAI({
75
+ apiKey: creds.apiKey,
76
+ baseURL,
77
+ maxRetries: 3,
78
+ timeout: STREAM_ABSOLUTE_BACKSTOP_MS,
79
+ dangerouslyAllowBrowser: creds.dangerouslyAllowBrowser,
80
+ });
81
+ this.log(`[OpenAICompatibleExecutor] Initialized ${this.providerName} @ ${baseURL} with model: ${this.model}`);
82
+ }
83
+ /**
84
+ * Invoke the OpenAI-compatible API.
85
+ * Returns: { message: { role, content, tool_calls }, usage: {...disjoint buckets} }
86
+ */
87
+ async invoke(messages, options = {}) {
88
+ const params = {
89
+ model: this.model,
90
+ messages: this.#formatMessages(messages),
91
+ };
92
+ // Pass through provider-specific params from model config (temperature, top_p, …).
93
+ Object.assign(params, this.#extractProviderParams());
94
+ if (options.tools && options.tools.length > 0) {
95
+ params.tools = options.tools.map(t => this.#formatTool(t));
96
+ // Explicit parallel tool calls — OpenAI-compatible default; set so it
97
+ // can't silently regress and matches parallel behavior across providers.
98
+ params.parallel_tool_calls = true;
99
+ }
100
+ if (options.tool_choice && options.tool_choice !== 'auto') {
101
+ params.tool_choice = this.#formatToolChoice(options.tool_choice);
102
+ }
103
+ this.log(`[OpenAICompatibleExecutor:${this.providerName}] Invoking:`, {
104
+ model: params.model,
105
+ temperature: params.temperature,
106
+ top_p: params.top_p,
107
+ tools: params.tools?.length || 0,
108
+ });
109
+ // STREAMED: consumeOpenAIStream reassembles content + tool_call arg deltas
110
+ // back into the exact non-streamed completion shape, so the extraction below
111
+ // is unchanged. Each chunk bumps the idle timer; a transient failure retries
112
+ // the whole prompt. stream_options.include_usage rides usage on the final
113
+ // chunk (guarded below — not all OpenAI-compatible hosts honor it).
114
+ const response = await streamWithRetry(async (guard) => {
115
+ const stream = await this.client.chat.completions.create({ ...params, stream: true, stream_options: { include_usage: true } }, { signal: guard.signal });
116
+ return await consumeOpenAIStream(stream, () => guard.bump());
117
+ }, {
118
+ externalSignal: options.signal,
119
+ isRetryable: (err) => this.isRetryableError(err),
120
+ log: (m) => this.log(m),
121
+ });
122
+ const choice = response.choices[0];
123
+ const message = choice.message;
124
+ // Automatic-cache providers report cached prefix tokens as a SUBSET of
125
+ // prompt_tokens (same convention as OpenAI direct). We subtract them out so
126
+ // input_tokens is the UNCACHED count and the four usage buckets stay
127
+ // disjoint across providers — otherwise cached reads get billed twice (once
128
+ // as input, once as read). cache_creation is 0: these providers have no
129
+ // billed write concept.
130
+ //
131
+ // Field location is NOT consistent across OpenAI-compatible hosts. Read all
132
+ // known spellings so a cache hit is never silently missed (a missed field
133
+ // reads as 0% hit rate → ~5x cost on our cache-heavy workload, and fails
134
+ // silently — the spec's highest-risk case):
135
+ // - OpenAI direct / Together dedicated-inference: nested
136
+ // prompt_tokens_details.cached_tokens
137
+ // - Together OpenAI-compat surface: top-level cached_tokens
138
+ // - DeepSeek: prompt_cache_hit_tokens (with prompt_cache_miss_tokens)
139
+ // All three are a SUBSET of prompt_tokens, so subtracting keeps input
140
+ // uncached. NOTE: prompt_tokens-is-cache-inclusive must be confirmed with a
141
+ // live Together call; if a host reports cached ADDITIVELY, this subtraction
142
+ // under-counts input and needs a per-host flag.
143
+ const usageTyped = response.usage;
144
+ const cachedTokens = usageTyped?.prompt_tokens_details?.cached_tokens ??
145
+ usageTyped?.cached_tokens ??
146
+ usageTyped?.prompt_cache_hit_tokens ??
147
+ 0;
148
+ // Streamed usage rides the final include_usage chunk. Real OpenAI/Together
149
+ // send it, but guard so a host that omits it yields 0s instead of a crash
150
+ // on response.usage!. If a host silently drops usage, cost → 0 and the
151
+ // credit engine under-bills — LIVE-VERIFY Together honors include_usage.
152
+ const promptTokens = response.usage?.prompt_tokens ?? 0;
153
+ const completionTokens = response.usage?.completion_tokens ?? 0;
154
+ return {
155
+ message: {
156
+ role: message.role,
157
+ content: message.content || '',
158
+ tool_calls: this.#extractToolCalls(message.tool_calls),
159
+ },
160
+ usage: {
161
+ input_tokens: Math.max(0, promptTokens - cachedTokens),
162
+ output_tokens: completionTokens,
163
+ cache_read_input_tokens: cachedTokens,
164
+ cache_creation_input_tokens: 0,
165
+ },
166
+ };
167
+ }
168
+ hasToolCalls(message) {
169
+ return Boolean(message?.tool_calls && message.tool_calls.length > 0);
170
+ }
171
+ /** Format messages for the OpenAI wire format. */
172
+ #formatMessages(messages) {
173
+ return messages.map(msg => {
174
+ if (msg.role === 'tool') {
175
+ return { role: 'tool', tool_call_id: msg.tool_call_id, content: msg.content };
176
+ }
177
+ return { role: msg.role, content: msg.content };
178
+ });
179
+ }
180
+ /** Format a tool definition to OpenAI format from any of our known shapes. */
181
+ #formatTool(tool) {
182
+ if (tool.type === 'function' && tool.function)
183
+ return tool;
184
+ if (tool.function)
185
+ return { type: 'function', function: tool.function };
186
+ if (tool.input_schema) {
187
+ return {
188
+ type: 'function',
189
+ function: { name: tool.name, description: tool.description || '', parameters: tool.input_schema },
190
+ };
191
+ }
192
+ if (tool.parameters) {
193
+ return {
194
+ type: 'function',
195
+ function: { name: tool.name, description: tool.description || '', parameters: tool.parameters },
196
+ };
197
+ }
198
+ this.log(`[OpenAICompatibleExecutor:${this.providerName}] Warning: Unrecognized tool format:`, JSON.stringify(tool));
199
+ return {
200
+ type: 'function',
201
+ function: {
202
+ name: tool.name || 'unknown',
203
+ description: tool.description || '',
204
+ parameters: tool.parameters || tool.input_schema || { type: 'object', properties: {} },
205
+ },
206
+ };
207
+ }
208
+ /** Format tool_choice to OpenAI format. */
209
+ #formatToolChoice(toolChoice) {
210
+ if (typeof toolChoice === 'string') {
211
+ if (toolChoice === 'required' || toolChoice === 'any')
212
+ return 'required';
213
+ return 'auto';
214
+ }
215
+ if (toolChoice.type === 'function' || toolChoice.function?.name)
216
+ return toolChoice;
217
+ // Anthropic format: { type: "tool", name: "..." }
218
+ if (toolChoice.type === 'tool' && toolChoice.name) {
219
+ return { type: 'function', function: { name: toolChoice.name } };
220
+ }
221
+ return 'auto';
222
+ }
223
+ /** Extract tool calls from an OpenAI-compatible response. */
224
+ #extractToolCalls(toolCalls) {
225
+ if (!toolCalls || toolCalls.length === 0)
226
+ return [];
227
+ return toolCalls.map(tc => ({
228
+ id: tc.id,
229
+ name: tc.function.name,
230
+ args: JSON.parse(tc.function.arguments),
231
+ }));
232
+ }
233
+ /** Provider-specific params from model config metadata (excludes non-API keys). */
234
+ #extractProviderParams() {
235
+ const metadata = this.primaryModelConfig.metadata || {};
236
+ // Strip keys that are adapter config, not model-call params.
237
+ const { displayName, baseURL, cacheMode, quantization, ...providerParams } = metadata;
238
+ return providerParams;
239
+ }
240
+ }
241
+ //# sourceMappingURL=openaiCompatible.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openaiCompatible.js","sourceRoot":"","sources":["../../src/providers/openaiCompatible.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * streaming.ts — shared streaming orchestration for the provider adapters.
3
+ *
4
+ * WHY: model responses used to be awaited as a single non-streamed completion,
5
+ * so the SDK's per-attempt `timeout` measured *total* time-to-complete. A
6
+ * legitimately long-but-progressing response (a full slide deck, a long report)
7
+ * could exceed that ceiling, get retried — re-sending the whole prompt and
8
+ * re-billing each attempt — and eventually fail as "Request timed out".
9
+ *
10
+ * Streaming removes the ceiling: we consume the provider's token stream and
11
+ * reset an INTER-CHUNK IDLE timer on every event. A response that keeps making
12
+ * progress never times out no matter how long it runs; only a genuinely STALLED
13
+ * stream (no token for `idleTimeoutMs`) is aborted and retried. A generous
14
+ * ABSOLUTE BACKSTOP guards the pathological "one token every 59 s forever" case.
15
+ *
16
+ * This module owns the timeout/abort/retry *orchestration* (provider-agnostic).
17
+ * Each provider adapter owns its SDK-specific stream consumption and assembles
18
+ * the SAME final response object its non-streamed path produced — so the
19
+ * executor's consumption (and token accounting) is byte-for-byte unchanged.
20
+ */
21
+ /** No token for this long → the stream is stalled; abort + retry the attempt. */
22
+ export declare const STREAM_IDLE_TIMEOUT_MS: number;
23
+ /** Absolute per-attempt ceiling. Only the pathological dribble case reaches it;
24
+ * a steadily-streaming multi-minute response never does. It is TERMINAL (not
25
+ * retried) so a misbehaving stream can't be retried 3× and blow the worker's
26
+ * 14-min checkpoint. Comfortably above any legitimate single-turn duration. */
27
+ export declare const STREAM_ABSOLUTE_BACKSTOP_MS: number;
28
+ /** Whole-attempt retries on a transient mid-stream failure (idle stall, network
29
+ * drop, 5xx after the stream opened). Matches the pre-existing retry budget. */
30
+ export declare const STREAM_MAX_RETRIES: number;
31
+ export type StreamAbortReason = 'idle' | 'backstop' | 'external';
32
+ /** Raised when the guard aborts a stream. `reason` decides retry policy:
33
+ * `idle` is transient (retry); `backstop` and `external` are terminal. */
34
+ export declare class StreamAbortError extends Error {
35
+ reason: StreamAbortReason;
36
+ constructor(reason: StreamAbortReason, message: string);
37
+ }
38
+ export interface StreamGuard {
39
+ /** Pass to the provider SDK's stream call; aborts on idle/backstop/external. */
40
+ readonly signal: AbortSignal;
41
+ /** Call on every stream event/chunk to reset the idle timer. */
42
+ bump(): void;
43
+ /** Why the guard aborted, or null if it hasn't. */
44
+ reason(): StreamAbortReason | null;
45
+ /** Clear all timers + listeners. Idempotent; always call in a finally. */
46
+ dispose(): void;
47
+ }
48
+ export interface StreamGuardOptions {
49
+ idleTimeoutMs?: number;
50
+ backstopMs?: number;
51
+ /** The caller's AbortSignal (backend stop/timeout). Aborts the stream with
52
+ * reason `external` — terminal, never retried. */
53
+ externalSignal?: AbortSignal;
54
+ }
55
+ /**
56
+ * Build an abort guard around a single stream attempt: an idle timer reset by
57
+ * `bump()`, an absolute backstop, and linkage to an optional external signal.
58
+ */
59
+ export declare function createStreamGuard(opts?: StreamGuardOptions): StreamGuard;
60
+ /** True for the abort error a provider SDK throws when we call `controller.abort()`. */
61
+ export declare function isAbortError(err: any): boolean;
62
+ export interface StreamRetryOptions {
63
+ maxRetries?: number;
64
+ idleTimeoutMs?: number;
65
+ backstopMs?: number;
66
+ externalSignal?: AbortSignal;
67
+ /** Classify a NON-abort mid-stream error as transient (retry) vs fatal. */
68
+ isRetryable?: (err: any) => boolean;
69
+ log?: (message: string) => void;
70
+ }
71
+ /**
72
+ * Run one stream `attempt` with a fresh guard, retrying the WHOLE prompt on a
73
+ * transient failure (idle stall or a caller-classified retryable error). The
74
+ * partial output is discarded on failure. Backstop and external aborts are
75
+ * terminal. Bounded by `maxRetries`.
76
+ */
77
+ export declare function streamWithRetry<T>(attempt: (guard: StreamGuard) => Promise<T>, opts?: StreamRetryOptions): Promise<T>;
78
+ /** The subset of an OpenAI chat completion the executor consumes. Assembling
79
+ * the stream back into this shape lets the non-streamed extraction run
80
+ * unchanged. */
81
+ export interface OpenAICompletionLike {
82
+ choices: Array<{
83
+ message: {
84
+ role: string;
85
+ content: string | null;
86
+ tool_calls?: Array<{
87
+ id: string;
88
+ type: 'function';
89
+ function: {
90
+ name: string;
91
+ arguments: string;
92
+ };
93
+ }>;
94
+ };
95
+ }>;
96
+ usage?: any;
97
+ }
98
+ /**
99
+ * Fold an OpenAI (or OpenAI-compatible) chat completion STREAM back into the
100
+ * non-streamed completion shape.
101
+ *
102
+ * Deltas arrive fragmented: `content` in pieces, and each tool call as an
103
+ * index-keyed run of deltas — the id + function.name usually in the first, the
104
+ * JSON `arguments` string across many. We reassemble per index and join in
105
+ * index order. Usage arrives on the final chunk (requires
106
+ * `stream_options.include_usage: true`).
107
+ */
108
+ export declare function consumeOpenAIStream(stream: AsyncIterable<any>, bump: () => void): Promise<OpenAICompletionLike>;
109
+ //# sourceMappingURL=streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../../src/providers/streaming.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,QACwB,CAAC;AAE5D;;;gFAGgF;AAChF,eAAO,MAAM,2BAA2B,QACgB,CAAC;AAEzD;iFACiF;AACjF,eAAO,MAAM,kBAAkB,QAAmD,CAAC;AAEnF,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjE;2EAC2E;AAC3E,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,MAAM,EAAE,iBAAiB,CAAC;gBACd,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM;CAKvD;AAED,MAAM,WAAW,WAAW;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,gEAAgE;IAChE,IAAI,IAAI,IAAI,CAAC;IACb,mDAAmD;IACnD,MAAM,IAAI,iBAAiB,GAAG,IAAI,CAAC;IACnC,0EAA0E;IAC1E,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;uDACmD;IACnD,cAAc,CAAC,EAAE,WAAW,CAAC;CAC9B;AAOD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,kBAAuB,GAAG,WAAW,CAqD5E;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAI9C;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,WAAW,CAAC;IAC7B,2EAA2E;IAC3E,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;IACpC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,OAAO,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAC3C,IAAI,GAAE,kBAAuB,GAC5B,OAAO,CAAC,CAAC,CAAC,CAyCZ;AAMD;;iBAEiB;AACjB,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAC;YACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;YACvB,UAAU,CAAC,EAAE,KAAK,CAAC;gBAAE,EAAE,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,UAAU,CAAC;gBAAC,QAAQ,EAAE;oBAAE,IAAI,EAAE,MAAM,CAAC;oBAAC,SAAS,EAAE,MAAM,CAAA;iBAAE,CAAA;aAAE,CAAC,CAAC;SACrG,CAAC;KACH,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,GAAG,CAAC;CACb;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,EAC1B,IAAI,EAAE,MAAM,IAAI,GACf,OAAO,CAAC,oBAAoB,CAAC,CAgD/B"}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * streaming.ts — shared streaming orchestration for the provider adapters.
3
+ *
4
+ * WHY: model responses used to be awaited as a single non-streamed completion,
5
+ * so the SDK's per-attempt `timeout` measured *total* time-to-complete. A
6
+ * legitimately long-but-progressing response (a full slide deck, a long report)
7
+ * could exceed that ceiling, get retried — re-sending the whole prompt and
8
+ * re-billing each attempt — and eventually fail as "Request timed out".
9
+ *
10
+ * Streaming removes the ceiling: we consume the provider's token stream and
11
+ * reset an INTER-CHUNK IDLE timer on every event. A response that keeps making
12
+ * progress never times out no matter how long it runs; only a genuinely STALLED
13
+ * stream (no token for `idleTimeoutMs`) is aborted and retried. A generous
14
+ * ABSOLUTE BACKSTOP guards the pathological "one token every 59 s forever" case.
15
+ *
16
+ * This module owns the timeout/abort/retry *orchestration* (provider-agnostic).
17
+ * Each provider adapter owns its SDK-specific stream consumption and assembles
18
+ * the SAME final response object its non-streamed path produced — so the
19
+ * executor's consumption (and token accounting) is byte-for-byte unchanged.
20
+ */
21
+ /** No token for this long → the stream is stalled; abort + retry the attempt. */
22
+ export const STREAM_IDLE_TIMEOUT_MS = Number(process.env.AGNT_STREAM_IDLE_TIMEOUT_MS) || 60_000;
23
+ /** Absolute per-attempt ceiling. Only the pathological dribble case reaches it;
24
+ * a steadily-streaming multi-minute response never does. It is TERMINAL (not
25
+ * retried) so a misbehaving stream can't be retried 3× and blow the worker's
26
+ * 14-min checkpoint. Comfortably above any legitimate single-turn duration. */
27
+ export const STREAM_ABSOLUTE_BACKSTOP_MS = Number(process.env.AGNT_STREAM_BACKSTOP_MS) || 600_000;
28
+ /** Whole-attempt retries on a transient mid-stream failure (idle stall, network
29
+ * drop, 5xx after the stream opened). Matches the pre-existing retry budget. */
30
+ export const STREAM_MAX_RETRIES = Number(process.env.AGNT_STREAM_MAX_RETRIES) || 3;
31
+ /** Raised when the guard aborts a stream. `reason` decides retry policy:
32
+ * `idle` is transient (retry); `backstop` and `external` are terminal. */
33
+ export class StreamAbortError extends Error {
34
+ reason;
35
+ constructor(reason, message) {
36
+ super(message);
37
+ this.name = 'StreamAbortError';
38
+ this.reason = reason;
39
+ }
40
+ }
41
+ function unref(t) {
42
+ // Don't let the idle/backstop timer keep the event loop (or a Lambda) alive.
43
+ if (t && typeof t.unref === 'function')
44
+ t.unref();
45
+ }
46
+ /**
47
+ * Build an abort guard around a single stream attempt: an idle timer reset by
48
+ * `bump()`, an absolute backstop, and linkage to an optional external signal.
49
+ */
50
+ export function createStreamGuard(opts = {}) {
51
+ const idleMs = opts.idleTimeoutMs ?? STREAM_IDLE_TIMEOUT_MS;
52
+ const backstopMs = opts.backstopMs ?? STREAM_ABSOLUTE_BACKSTOP_MS;
53
+ const controller = new AbortController();
54
+ let abortedReason = null;
55
+ let idleTimer = null;
56
+ let backstopTimer = null;
57
+ let externalCleanup = null;
58
+ const abort = (reason) => {
59
+ if (abortedReason)
60
+ return; // first reason wins
61
+ abortedReason = reason;
62
+ clearTimers();
63
+ controller.abort();
64
+ };
65
+ const clearTimers = () => {
66
+ if (idleTimer) {
67
+ clearTimeout(idleTimer);
68
+ idleTimer = null;
69
+ }
70
+ if (backstopTimer) {
71
+ clearTimeout(backstopTimer);
72
+ backstopTimer = null;
73
+ }
74
+ };
75
+ const bump = () => {
76
+ if (abortedReason)
77
+ return;
78
+ if (idleTimer)
79
+ clearTimeout(idleTimer);
80
+ idleTimer = setTimeout(() => abort('idle'), idleMs);
81
+ unref(idleTimer);
82
+ };
83
+ backstopTimer = setTimeout(() => abort('backstop'), backstopMs);
84
+ unref(backstopTimer);
85
+ if (opts.externalSignal) {
86
+ const ext = opts.externalSignal;
87
+ if (ext.aborted) {
88
+ abort('external');
89
+ }
90
+ else {
91
+ const onAbort = () => abort('external');
92
+ ext.addEventListener('abort', onAbort, { once: true });
93
+ externalCleanup = () => ext.removeEventListener('abort', onAbort);
94
+ }
95
+ }
96
+ bump(); // arm the idle timer for the connection-open window
97
+ return {
98
+ signal: controller.signal,
99
+ bump,
100
+ reason: () => abortedReason,
101
+ dispose: () => {
102
+ clearTimers();
103
+ if (externalCleanup) {
104
+ externalCleanup();
105
+ externalCleanup = null;
106
+ }
107
+ },
108
+ };
109
+ }
110
+ /** True for the abort error a provider SDK throws when we call `controller.abort()`. */
111
+ export function isAbortError(err) {
112
+ const name = err?.name ?? err?.constructor?.name;
113
+ if (name === 'AbortError' || name === 'APIUserAbortError')
114
+ return true;
115
+ return typeof err?.message === 'string' && /\baborted\b/i.test(err.message);
116
+ }
117
+ /**
118
+ * Run one stream `attempt` with a fresh guard, retrying the WHOLE prompt on a
119
+ * transient failure (idle stall or a caller-classified retryable error). The
120
+ * partial output is discarded on failure. Backstop and external aborts are
121
+ * terminal. Bounded by `maxRetries`.
122
+ */
123
+ export async function streamWithRetry(attempt, opts = {}) {
124
+ const maxRetries = opts.maxRetries ?? STREAM_MAX_RETRIES;
125
+ const isRetryable = opts.isRetryable ?? (() => true);
126
+ let lastError;
127
+ for (let i = 0; i < maxRetries; i++) {
128
+ if (opts.externalSignal?.aborted) {
129
+ throw new StreamAbortError('external', '[streaming] aborted before attempt');
130
+ }
131
+ const guard = createStreamGuard({
132
+ idleTimeoutMs: opts.idleTimeoutMs,
133
+ backstopMs: opts.backstopMs,
134
+ externalSignal: opts.externalSignal,
135
+ });
136
+ try {
137
+ return await attempt(guard);
138
+ }
139
+ catch (err) {
140
+ // Map a guard-triggered abort to a typed error carrying its reason.
141
+ const reason = guard.reason();
142
+ const mapped = reason && isAbortError(err)
143
+ ? new StreamAbortError(reason, `[streaming] stream aborted: ${reason}`)
144
+ : err;
145
+ lastError = mapped;
146
+ // Terminal: the caller asked to stop, or the stream dribbled to the
147
+ // backstop — retrying either would be wrong/wasteful.
148
+ if (mapped instanceof StreamAbortError && mapped.reason !== 'idle') {
149
+ throw mapped;
150
+ }
151
+ const retryable = mapped instanceof StreamAbortError ? mapped.reason === 'idle' : isRetryable(mapped);
152
+ if (!retryable || i === maxRetries - 1)
153
+ throw mapped;
154
+ opts.log?.(`[streaming] attempt ${i + 1}/${maxRetries} failed (${mapped?.message ?? mapped}) — discarding partial, retrying`);
155
+ }
156
+ finally {
157
+ guard.dispose();
158
+ }
159
+ }
160
+ throw lastError;
161
+ }
162
+ /**
163
+ * Fold an OpenAI (or OpenAI-compatible) chat completion STREAM back into the
164
+ * non-streamed completion shape.
165
+ *
166
+ * Deltas arrive fragmented: `content` in pieces, and each tool call as an
167
+ * index-keyed run of deltas — the id + function.name usually in the first, the
168
+ * JSON `arguments` string across many. We reassemble per index and join in
169
+ * index order. Usage arrives on the final chunk (requires
170
+ * `stream_options.include_usage: true`).
171
+ */
172
+ export async function consumeOpenAIStream(stream, bump) {
173
+ let role = 'assistant';
174
+ let content = '';
175
+ let sawContent = false;
176
+ let usage = undefined;
177
+ const byIndex = new Map();
178
+ for await (const chunk of stream) {
179
+ bump();
180
+ const choice = chunk?.choices?.[0];
181
+ if (choice) {
182
+ const delta = choice.delta || {};
183
+ if (delta.role)
184
+ role = delta.role;
185
+ if (typeof delta.content === 'string') {
186
+ content += delta.content;
187
+ sawContent = true;
188
+ }
189
+ if (Array.isArray(delta.tool_calls)) {
190
+ for (const tc of delta.tool_calls) {
191
+ const idx = typeof tc.index === 'number' ? tc.index : 0;
192
+ let acc = byIndex.get(idx);
193
+ if (!acc) {
194
+ acc = { id: '', name: '', args: '' };
195
+ byIndex.set(idx, acc);
196
+ }
197
+ if (tc.id)
198
+ acc.id = tc.id;
199
+ if (tc.function?.name)
200
+ acc.name += tc.function.name;
201
+ if (typeof tc.function?.arguments === 'string')
202
+ acc.args += tc.function.arguments;
203
+ }
204
+ }
205
+ }
206
+ // Usage rides its own final chunk (choices may be empty there).
207
+ if (chunk?.usage)
208
+ usage = chunk.usage;
209
+ }
210
+ const tool_calls = byIndex.size
211
+ ? [...byIndex.entries()]
212
+ .sort((a, b) => a[0] - b[0])
213
+ // Default empty args to "{}" — a zero-arg tool call may stream an id+name
214
+ // with no `arguments` fragment (real OpenAI always sends "{}", but
215
+ // DeepSeek / OpenAI-compatible endpoints may not). The downstream
216
+ // extractor does JSON.parse(arguments), which throws on "". Mirror the
217
+ // non-streamed shape so a no-arg tool call parses to {} instead of crashing.
218
+ .map(([, v]) => ({ id: v.id, type: 'function', function: { name: v.name, arguments: v.args || '{}' } }))
219
+ : undefined;
220
+ return {
221
+ // Mirror the non-streamed shape: content is null when the turn is tool-only.
222
+ choices: [{ message: { role, content: sawContent ? content : (tool_calls ? null : ''), tool_calls } }],
223
+ usage,
224
+ };
225
+ }
226
+ //# sourceMappingURL=streaming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.js","sourceRoot":"","sources":["../../src/providers/streaming.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -48,14 +48,21 @@ export interface ProviderCredentials {
48
48
  accessKeyId?: string;
49
49
  secretAccessKey?: string;
50
50
  };
51
- deepseek?: {
52
- apiKey: string;
53
- dangerouslyAllowBrowser?: boolean;
54
- };
51
+ deepseek?: OpenAICompatibleCredentials;
55
52
  google?: {
56
53
  apiKey: string;
57
54
  dangerouslyAllowBrowser?: boolean;
58
55
  };
56
+ together?: OpenAICompatibleCredentials;
57
+ fireworks?: OpenAICompatibleCredentials;
58
+ deepinfra?: OpenAICompatibleCredentials;
59
+ [provider: string]: any;
60
+ }
61
+ export interface OpenAICompatibleCredentials {
62
+ apiKey: string;
63
+ /** Override the provider registry / model-metadata base URL. */
64
+ baseURL?: string;
65
+ dangerouslyAllowBrowser?: boolean;
59
66
  }
60
67
  export interface ToolHandler {
61
68
  execute: (args: any) => Promise<any>;
@@ -159,6 +166,10 @@ export interface InvokeOptions {
159
166
  * no follow-up turn to hit the cache — avoids paying the 25% write surcharge
160
167
  * with zero benefit. Agent/Prime multi-turn runs should NOT set this. */
161
168
  disableCache?: boolean;
169
+ /** Caller's abort signal (backend stop/timeout). Threaded to the provider's
170
+ * streaming call so an in-flight token stream stops cleanly. Set by
171
+ * BaseExecutor from its per-run AbortController (see cancel()). */
172
+ signal?: AbortSignal;
162
173
  }
163
174
  /** Leaf condition: variable op value */
164
175
  export interface LeafCondition {