@fgv/ts-extras 5.1.0-37 → 5.1.0-39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/packlets/ai-assist/apiClient.js +66 -111
- package/dist/packlets/ai-assist/apiClient.js.map +1 -1
- package/dist/packlets/ai-assist/converters.js +16 -3
- package/dist/packlets/ai-assist/converters.js.map +1 -1
- package/dist/packlets/ai-assist/embeddingClient.js +5 -6
- package/dist/packlets/ai-assist/embeddingClient.js.map +1 -1
- package/dist/packlets/ai-assist/imageOptionsResolver.js +0 -19
- package/dist/packlets/ai-assist/imageOptionsResolver.js.map +1 -1
- package/dist/packlets/ai-assist/index.js +2 -2
- package/dist/packlets/ai-assist/index.js.map +1 -1
- package/dist/packlets/ai-assist/model.js +139 -7
- package/dist/packlets/ai-assist/model.js.map +1 -1
- package/dist/packlets/ai-assist/registry.js +58 -51
- package/dist/packlets/ai-assist/registry.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js +38 -11
- package/dist/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +187 -36
- package/dist/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/common.js +13 -0
- package/dist/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js +12 -4
- package/dist/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js +6 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js +10 -3
- package/dist/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js +5 -2
- package/dist/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
- package/dist/packlets/ai-assist/streamingClient.js +16 -16
- package/dist/packlets/ai-assist/streamingClient.js.map +1 -1
- package/dist/packlets/crypto-utils/converters.js +18 -0
- package/dist/packlets/crypto-utils/converters.js.map +1 -1
- package/dist/packlets/crypto-utils/index.browser.js +2 -2
- package/dist/packlets/crypto-utils/index.browser.js.map +1 -1
- package/dist/packlets/crypto-utils/index.js +2 -2
- package/dist/packlets/crypto-utils/index.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/keyStore.js +19 -4
- package/dist/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/dist/packlets/crypto-utils/keystore/model.js.map +1 -1
- package/dist/packlets/crypto-utils/model.js.map +1 -1
- package/dist/packlets/crypto-utils/spkiHelpers.js +104 -25
- package/dist/packlets/crypto-utils/spkiHelpers.js.map +1 -1
- package/dist/ts-extras.d.ts +377 -132
- package/dist/tsdoc-metadata.json +1 -1
- package/lib/packlets/ai-assist/apiClient.d.ts +12 -2
- package/lib/packlets/ai-assist/apiClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/apiClient.js +65 -110
- package/lib/packlets/ai-assist/apiClient.js.map +1 -1
- package/lib/packlets/ai-assist/converters.d.ts +8 -2
- package/lib/packlets/ai-assist/converters.d.ts.map +1 -1
- package/lib/packlets/ai-assist/converters.js +17 -4
- package/lib/packlets/ai-assist/converters.js.map +1 -1
- package/lib/packlets/ai-assist/embeddingClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/embeddingClient.js +4 -5
- package/lib/packlets/ai-assist/embeddingClient.js.map +1 -1
- package/lib/packlets/ai-assist/imageOptionsResolver.d.ts +0 -8
- package/lib/packlets/ai-assist/imageOptionsResolver.d.ts.map +1 -1
- package/lib/packlets/ai-assist/imageOptionsResolver.js +0 -19
- package/lib/packlets/ai-assist/imageOptionsResolver.js.map +1 -1
- package/lib/packlets/ai-assist/index.d.ts +3 -3
- package/lib/packlets/ai-assist/index.d.ts.map +1 -1
- package/lib/packlets/ai-assist/index.js +6 -1
- package/lib/packlets/ai-assist/index.js.map +1 -1
- package/lib/packlets/ai-assist/model.d.ts +181 -98
- package/lib/packlets/ai-assist/model.d.ts.map +1 -1
- package/lib/packlets/ai-assist/model.js +143 -8
- package/lib/packlets/ai-assist/model.js.map +1 -1
- package/lib/packlets/ai-assist/registry.d.ts.map +1 -1
- package/lib/packlets/ai-assist/registry.js +58 -51
- package/lib/packlets/ai-assist/registry.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js +37 -10
- package/lib/packlets/ai-assist/streamingAdapters/anthropic.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts +82 -3
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js +187 -35
- package/lib/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts +24 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.js +14 -1
- package/lib/packlets/ai-assist/streamingAdapters/common.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts +10 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js +12 -4
- package/lib/packlets/ai-assist/streamingAdapters/gemini.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js +6 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiChat.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js +10 -3
- package/lib/packlets/ai-assist/streamingAdapters/openaiResponses.js.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/proxy.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js +5 -2
- package/lib/packlets/ai-assist/streamingAdapters/proxy.js.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.d.ts +1 -1
- package/lib/packlets/ai-assist/streamingClient.d.ts.map +1 -1
- package/lib/packlets/ai-assist/streamingClient.js +15 -15
- package/lib/packlets/ai-assist/streamingClient.js.map +1 -1
- package/lib/packlets/crypto-utils/converters.d.ts +9 -1
- package/lib/packlets/crypto-utils/converters.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/converters.js +19 -1
- package/lib/packlets/crypto-utils/converters.js.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.d.ts +1 -1
- package/lib/packlets/crypto-utils/index.browser.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.browser.js +6 -2
- package/lib/packlets/crypto-utils/index.browser.js.map +1 -1
- package/lib/packlets/crypto-utils/index.d.ts +1 -1
- package/lib/packlets/crypto-utils/index.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/index.js +6 -2
- package/lib/packlets/crypto-utils/index.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts +9 -2
- package/lib/packlets/crypto-utils/keystore/keyStore.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/keyStore.js +19 -4
- package/lib/packlets/crypto-utils/keystore/keyStore.js.map +1 -1
- package/lib/packlets/crypto-utils/keystore/model.d.ts +7 -0
- package/lib/packlets/crypto-utils/keystore/model.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/keystore/model.js.map +1 -1
- package/lib/packlets/crypto-utils/model.d.ts +11 -1
- package/lib/packlets/crypto-utils/model.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/model.js.map +1 -1
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts +59 -2
- package/lib/packlets/crypto-utils/spkiHelpers.d.ts.map +1 -1
- package/lib/packlets/crypto-utils/spkiHelpers.js +108 -25
- package/lib/packlets/crypto-utils/spkiHelpers.js.map +1 -1
- package/package.json +7 -7
|
@@ -40,6 +40,15 @@ interface IToolCallResult {
|
|
|
40
40
|
* (all block types in original stream order) and appends a user turn
|
|
41
41
|
* with `tool_result` blocks for each executed tool call.
|
|
42
42
|
*
|
|
43
|
+
* **Single source of truth for the id (id-correlation fix).** Each
|
|
44
|
+
* `tool_result.tool_use_id` is drawn from — and validated against — the set of
|
|
45
|
+
* assistant `tool_use.id`s actually present in the accumulation buffer. The id
|
|
46
|
+
* is NEVER derived from the tool name: a tool name can never match a `toolu_*`
|
|
47
|
+
* id, so a name fallback produces exactly the "malformed identifier" the
|
|
48
|
+
* provider rejects. If any tool result cannot be correlated to a buffered
|
|
49
|
+
* `tool_use` block with a non-empty id, the build fails loud (naming the tool)
|
|
50
|
+
* rather than emitting a malformed continuation.
|
|
51
|
+
*
|
|
43
52
|
* **Constraint (E3):** The returned continuation does NOT include a forced
|
|
44
53
|
* `tool_choice` field. When thinking is active, Anthropic rejects
|
|
45
54
|
* `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`
|
|
@@ -48,7 +57,7 @@ interface IToolCallResult {
|
|
|
48
57
|
*
|
|
49
58
|
* @internal
|
|
50
59
|
*/
|
|
51
|
-
export declare function buildAnthropicContinuation(accBuffer: Map<number, IAccumulatedBlock>, toolResults: IToolCallResult[]): IAiClientToolContinuation
|
|
60
|
+
export declare function buildAnthropicContinuation(accBuffer: Map<number, IAccumulatedBlock>, toolResults: IToolCallResult[], logger?: Logging.ILogger): Result<IAiClientToolContinuation>;
|
|
52
61
|
/**
|
|
53
62
|
* Builds the OpenAI / xAI Responses API follow-up input items for a
|
|
54
63
|
* client-tool round-trip.
|
|
@@ -57,9 +66,16 @@ export declare function buildAnthropicContinuation(accBuffer: Map<number, IAccum
|
|
|
57
66
|
* `function_call_output` items (the harness execution result), one pair
|
|
58
67
|
* per executed tool call.
|
|
59
68
|
*
|
|
69
|
+
* **Single source of truth for the id (id-correlation fix).** Each
|
|
70
|
+
* `function_call_output.call_id` is drawn from — and validated against — the
|
|
71
|
+
* set of `function_call.call_id`s in the accumulation map. It is NEVER derived
|
|
72
|
+
* from the tool name (the OpenAI parity of the Anthropic `tool_use_id` fix). A
|
|
73
|
+
* missing / empty / unmatched call id fails the build loud rather than emitting
|
|
74
|
+
* a continuation the provider would reject.
|
|
75
|
+
*
|
|
60
76
|
* @internal
|
|
61
77
|
*/
|
|
62
|
-
export declare function buildOpenAiContinuation(calls: Map<string, IAccumulatedFunctionCall>, toolResults: IToolCallResult[]): IAiClientToolContinuation
|
|
78
|
+
export declare function buildOpenAiContinuation(calls: Map<string, IAccumulatedFunctionCall>, toolResults: IToolCallResult[], logger?: Logging.ILogger): Result<IAiClientToolContinuation>;
|
|
63
79
|
/**
|
|
64
80
|
* Builds the Gemini follow-up contents for a client-tool round-trip.
|
|
65
81
|
*
|
|
@@ -67,9 +83,42 @@ export declare function buildOpenAiContinuation(calls: Map<string, IAccumulatedF
|
|
|
67
83
|
* user turn with `functionResponse` parts (correlation by tool name, since
|
|
68
84
|
* Gemini does not assign call IDs).
|
|
69
85
|
*
|
|
86
|
+
* When thinking is enabled, Gemini stamps an opaque `thoughtSignature` on each
|
|
87
|
+
* `functionCall` part and requires it echoed back, verbatim, as a sibling of
|
|
88
|
+
* `functionCall` on the continuation's model turn — otherwise the follow-up is
|
|
89
|
+
* rejected with "Function call is missing a thought_signature in functionCall
|
|
90
|
+
* parts". The captured signature (see {@link IAccumulatedGeminiFunctionCall})
|
|
91
|
+
* is replayed here only when present; the key is omitted entirely when thinking
|
|
92
|
+
* was disabled. See https://ai.google.dev/gemini-api/docs/thought-signatures.
|
|
93
|
+
*
|
|
70
94
|
* @internal
|
|
71
95
|
*/
|
|
72
96
|
export declare function buildGeminiContinuation(calls: IAccumulatedGeminiFunctionCall[], toolResults: IToolCallResult[]): IAiClientToolContinuation;
|
|
97
|
+
/**
|
|
98
|
+
* Decision returned by an {@link IExecuteClientToolTurnParams.onBeforeToolExecute}
|
|
99
|
+
* gate for a single client-tool call.
|
|
100
|
+
*
|
|
101
|
+
* @remarks
|
|
102
|
+
* - `{ action: 'proceed' }` — run the tool's `execute` normally.
|
|
103
|
+
* - `{ action: 'deny', reason }` — do NOT run `execute`; a synthesized tool-result
|
|
104
|
+
* carrying `reason` is fed into the continuation exactly like a normal result so
|
|
105
|
+
* the model can react, and a `client-tool-result` event is emitted for the call.
|
|
106
|
+
* The turn continues.
|
|
107
|
+
*
|
|
108
|
+
* A gate that wants to signal a hard error (as opposed to a deliberate deny) should
|
|
109
|
+
* return `Result.fail` (or reject) from the callback. Note this is NOT the same as a
|
|
110
|
+
* `tool.execute` failure: an `execute` failure yields an `isError` tool-result and the
|
|
111
|
+
* **turn continues**, whereas a gate `fail`/reject **terminates the turn** — it fails
|
|
112
|
+
* `nextTurn` (and emits an inline `error` event) rather than synthesizing a deny.
|
|
113
|
+
*
|
|
114
|
+
* @public
|
|
115
|
+
*/
|
|
116
|
+
export type IToolExecutionDecision = {
|
|
117
|
+
readonly action: 'proceed';
|
|
118
|
+
} | {
|
|
119
|
+
readonly action: 'deny';
|
|
120
|
+
readonly reason: string;
|
|
121
|
+
};
|
|
73
122
|
/**
|
|
74
123
|
* Parameters for {@link AiAssist.executeClientToolTurn}.
|
|
75
124
|
*
|
|
@@ -112,7 +161,10 @@ export interface IExecuteClientToolTurnParams extends IChatRequest {
|
|
|
112
161
|
* normalized-message path strips provider-native fields.
|
|
113
162
|
*/
|
|
114
163
|
readonly continuationMessages?: ReadonlyArray<JsonObject>;
|
|
115
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Sampling temperature. Sent to the provider only when explicitly provided; omitted otherwise
|
|
166
|
+
* so the provider's own default applies (current-gen models reject a caller-supplied default).
|
|
167
|
+
*/
|
|
116
168
|
readonly temperature?: number;
|
|
117
169
|
/** Server-side tools to include. */
|
|
118
170
|
readonly tools?: ReadonlyArray<AiServerToolConfig>;
|
|
@@ -136,6 +188,22 @@ export interface IExecuteClientToolTurnParams extends IChatRequest {
|
|
|
136
188
|
readonly resolvedThinking?: IResolvedThinkingConfig;
|
|
137
189
|
/** Resolved model string (pre-resolved by the caller). When omitted, uses the descriptor's default model. */
|
|
138
190
|
readonly model?: string;
|
|
191
|
+
/**
|
|
192
|
+
* Optional host gate invoked for each client-tool call **after** the model's raw
|
|
193
|
+
* args have been validated against the tool's `parametersSchema` and **before**
|
|
194
|
+
* `tool.execute(...)` runs. The full `tool` (including `tool.config.annotations`)
|
|
195
|
+
* and the validated `args` are passed so gate logic can key off a tool's behavior
|
|
196
|
+
* annotations (e.g. deny a `destructiveHint` call pending confirmation).
|
|
197
|
+
*
|
|
198
|
+
* Returning `{ action: 'proceed' }` (or omitting the callback) runs `execute`
|
|
199
|
+
* normally. Returning `{ action: 'deny', reason }` skips `execute` and synthesizes
|
|
200
|
+
* a denial tool-result carrying `reason` into the continuation (the model sees the
|
|
201
|
+
* denial and can react); **the turn continues**. A callback that returns `Result.fail`
|
|
202
|
+
* or rejects is a hard error that **terminates the turn** (fails `nextTurn` + emits an
|
|
203
|
+
* inline `error` event) — distinct from a `tool.execute` failure, which continues the
|
|
204
|
+
* turn with an `isError` result. A deny must therefore be explicit.
|
|
205
|
+
*/
|
|
206
|
+
readonly onBeforeToolExecute?: (tool: IAiClientTool, args: unknown) => Promise<Result<IToolExecutionDecision>>;
|
|
139
207
|
}
|
|
140
208
|
/**
|
|
141
209
|
* Return value of {@link AiAssist.executeClientToolTurn}.
|
|
@@ -155,6 +223,17 @@ export interface IExecuteClientToolTurnResult {
|
|
|
155
223
|
*/
|
|
156
224
|
readonly nextTurn: Promise<Result<IAiClientToolTurnResult>>;
|
|
157
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* True when a request would combine Gemini built-in grounding (a `web_search`
|
|
228
|
+
* server tool) with client (function) tools — a combination Gemini's
|
|
229
|
+
* `generateContent` API rejects with HTTP 400 (`INVALID_ARGUMENT`). Callers gate
|
|
230
|
+
* on `descriptor.apiFormat === 'gemini'` before consulting this; other providers
|
|
231
|
+
* accept the mix. Kept as a pure predicate so the conflict rule is unit-testable
|
|
232
|
+
* without a live stream.
|
|
233
|
+
*
|
|
234
|
+
* @internal
|
|
235
|
+
*/
|
|
236
|
+
export declare function hasGeminiToolConflict(tools: ReadonlyArray<AiServerToolConfig> | undefined, clientTools: ReadonlyArray<IAiClientTool>): boolean;
|
|
158
237
|
/**
|
|
159
238
|
* Orchestrates a single client-tool streaming turn for any supported provider.
|
|
160
239
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clientToolContinuationBuilder.d.ts","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.ts"],"names":[],"mappings":"AAoBA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAA4B,KAAK,OAAO,EAAE,MAAM,EAAW,MAAM,eAAe,CAAC;AACxF,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,aAAa,EAClB,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAE3B,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAG1E,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,KAAK,8BAA8B,EAAE,MAAM,UAAU,CAAC;AAU/D;;;GAGG;AACH,UAAU,eAAe;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"clientToolContinuationBuilder.d.ts","sourceRoot":"","sources":["../../../../src/packlets/ai-assist/streamingAdapters/clientToolContinuationBuilder.ts"],"names":[],"mappings":"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"}
|
|
@@ -42,6 +42,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
42
42
|
exports.buildAnthropicContinuation = buildAnthropicContinuation;
|
|
43
43
|
exports.buildOpenAiContinuation = buildOpenAiContinuation;
|
|
44
44
|
exports.buildGeminiContinuation = buildGeminiContinuation;
|
|
45
|
+
exports.hasGeminiToolConflict = hasGeminiToolConflict;
|
|
45
46
|
exports.executeClientToolTurn = executeClientToolTurn;
|
|
46
47
|
/**
|
|
47
48
|
* Per-provider continuation message builders and the `executeClientToolTurn`
|
|
@@ -70,6 +71,19 @@ const gemini_1 = require("./gemini");
|
|
|
70
71
|
// ============================================================================
|
|
71
72
|
// Anthropic continuation builder
|
|
72
73
|
// ============================================================================
|
|
74
|
+
/**
|
|
75
|
+
* Returns `true` when `id` is a usable correlation id — present and non-empty.
|
|
76
|
+
*
|
|
77
|
+
* `??` is NOT sufficient for this check: an empty string is a *bad* id (a
|
|
78
|
+
* `tool_use_id: ''` is rejected by Anthropic as a "malformed identifier") but
|
|
79
|
+
* `'' ?? fallback` yields `''`, silently passing the empty id through. Every id
|
|
80
|
+
* correlation in this module must use this predicate, never `??`.
|
|
81
|
+
*
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
function isUsableId(id) {
|
|
85
|
+
return id !== undefined && id.length > 0;
|
|
86
|
+
}
|
|
73
87
|
/**
|
|
74
88
|
* Builds the Anthropic follow-up messages for a client-tool round-trip.
|
|
75
89
|
*
|
|
@@ -77,6 +91,15 @@ const gemini_1 = require("./gemini");
|
|
|
77
91
|
* (all block types in original stream order) and appends a user turn
|
|
78
92
|
* with `tool_result` blocks for each executed tool call.
|
|
79
93
|
*
|
|
94
|
+
* **Single source of truth for the id (id-correlation fix).** Each
|
|
95
|
+
* `tool_result.tool_use_id` is drawn from — and validated against — the set of
|
|
96
|
+
* assistant `tool_use.id`s actually present in the accumulation buffer. The id
|
|
97
|
+
* is NEVER derived from the tool name: a tool name can never match a `toolu_*`
|
|
98
|
+
* id, so a name fallback produces exactly the "malformed identifier" the
|
|
99
|
+
* provider rejects. If any tool result cannot be correlated to a buffered
|
|
100
|
+
* `tool_use` block with a non-empty id, the build fails loud (naming the tool)
|
|
101
|
+
* rather than emitting a malformed continuation.
|
|
102
|
+
*
|
|
80
103
|
* **Constraint (E3):** The returned continuation does NOT include a forced
|
|
81
104
|
* `tool_choice` field. When thinking is active, Anthropic rejects
|
|
82
105
|
* `tool_choice: { type: 'any' }` and `tool_choice: { type: 'tool', ... }`
|
|
@@ -85,11 +108,14 @@ const gemini_1 = require("./gemini");
|
|
|
85
108
|
*
|
|
86
109
|
* @internal
|
|
87
110
|
*/
|
|
88
|
-
function buildAnthropicContinuation(accBuffer, toolResults) {
|
|
111
|
+
function buildAnthropicContinuation(accBuffer, toolResults, logger) {
|
|
89
112
|
// Reconstruct the assistant turn from the ordered accumulation buffer.
|
|
90
113
|
// Sort by buffer key (SSE index) to restore original stream order.
|
|
91
114
|
const sortedKeys = Array.from(accBuffer.keys()).sort((a, b) => a - b);
|
|
92
115
|
const assistantContent = [];
|
|
116
|
+
// The set of assistant tool_use ids — the single source of truth every
|
|
117
|
+
// tool_result.tool_use_id must be drawn from.
|
|
118
|
+
const bufferedToolUseIds = new Set();
|
|
93
119
|
for (const key of sortedKeys) {
|
|
94
120
|
const block = accBuffer.get(key);
|
|
95
121
|
/* c8 ignore next 1 - defensive: key always exists in map since we iterate its keys */
|
|
@@ -114,6 +140,12 @@ function buildAnthropicContinuation(accBuffer, toolResults) {
|
|
|
114
140
|
}
|
|
115
141
|
}
|
|
116
142
|
else if (block.type === 'tool_use') {
|
|
143
|
+
// A buffered tool_use with an empty id can never be referenced by a valid
|
|
144
|
+
// tool_result; emitting it would corrupt the assistant turn. Fail loud.
|
|
145
|
+
if (!isUsableId(block.id)) {
|
|
146
|
+
return (0, ts_utils_1.fail)(`Anthropic continuation: buffered tool_use block for tool '${block.name}' has an empty id; ` +
|
|
147
|
+
`cannot build a valid continuation`);
|
|
148
|
+
}
|
|
117
149
|
let parsedInput;
|
|
118
150
|
try {
|
|
119
151
|
/* c8 ignore next 1 - defensive: argsBuffer is JSON-parsed in the adapter before emitting client-tool-call-done */
|
|
@@ -124,6 +156,7 @@ function buildAnthropicContinuation(accBuffer, toolResults) {
|
|
|
124
156
|
parsedInput = {};
|
|
125
157
|
}
|
|
126
158
|
/* c8 ignore stop */
|
|
159
|
+
bufferedToolUseIds.add(block.id);
|
|
127
160
|
assistantContent.push({
|
|
128
161
|
type: 'tool_use',
|
|
129
162
|
id: block.id,
|
|
@@ -132,20 +165,36 @@ function buildAnthropicContinuation(accBuffer, toolResults) {
|
|
|
132
165
|
});
|
|
133
166
|
}
|
|
134
167
|
}
|
|
135
|
-
// Build user turn with tool_result blocks for each tool call.
|
|
136
|
-
|
|
137
|
-
|
|
168
|
+
// Build user turn with tool_result blocks for each tool call. Correlate each
|
|
169
|
+
// result to a buffered tool_use id — the SAME id that keys the assistant
|
|
170
|
+
// tool_use block — and fail loud on a missing / empty / mismatched id. A
|
|
171
|
+
// missing callId is a bug to surface, not paper over with a name fallback.
|
|
172
|
+
const userContent = [];
|
|
173
|
+
for (const r of toolResults) {
|
|
174
|
+
if (!isUsableId(r.callId)) {
|
|
175
|
+
return (0, ts_utils_1.fail)(`Anthropic continuation: tool '${r.toolName}' result has no call id (missing or empty); ` +
|
|
176
|
+
`cannot correlate it to an assistant tool_use block`);
|
|
177
|
+
}
|
|
178
|
+
if (!bufferedToolUseIds.has(r.callId)) {
|
|
179
|
+
return (0, ts_utils_1.fail)(`Anthropic continuation: tool '${r.toolName}' call id '${r.callId}' does not match any ` +
|
|
180
|
+
`buffered assistant tool_use block id`);
|
|
181
|
+
}
|
|
138
182
|
const block = {
|
|
139
183
|
type: 'tool_result',
|
|
140
184
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
141
|
-
tool_use_id:
|
|
185
|
+
tool_use_id: r.callId,
|
|
142
186
|
content: r.result
|
|
143
187
|
};
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
188
|
+
userContent.push(r.isError ? Object.assign(Object.assign({}, block), { is_error: true }) : block);
|
|
189
|
+
}
|
|
190
|
+
// Decisive diagnostic: the tool_use.id ↔ tool_result.tool_use_id pairing for
|
|
191
|
+
// the outgoing continuation. A failing turn is field-confirmable by capturing
|
|
192
|
+
// this line (the pairing is by construction matched here — divergence fails
|
|
193
|
+
// loud above, before reaching this point).
|
|
194
|
+
if (logger) {
|
|
195
|
+
const pairing = toolResults.map((r) => `${r.toolName}:${r.callId}`).join(', ');
|
|
196
|
+
logger.detail(`ai-assist:anthropic-continuation tool_use.id↔tool_result.tool_use_id [${pairing}]`);
|
|
197
|
+
}
|
|
149
198
|
const assistantMessage = {
|
|
150
199
|
role: 'assistant',
|
|
151
200
|
content: assistantContent
|
|
@@ -154,7 +203,7 @@ function buildAnthropicContinuation(accBuffer, toolResults) {
|
|
|
154
203
|
role: 'user',
|
|
155
204
|
content: userContent
|
|
156
205
|
};
|
|
157
|
-
return {
|
|
206
|
+
return (0, ts_utils_1.succeed)({
|
|
158
207
|
messages: [assistantMessage, userMessage],
|
|
159
208
|
toolCallsSummary: toolResults.map((r) => ({
|
|
160
209
|
toolName: r.toolName,
|
|
@@ -163,7 +212,7 @@ function buildAnthropicContinuation(accBuffer, toolResults) {
|
|
|
163
212
|
result: r.result,
|
|
164
213
|
isError: r.isError
|
|
165
214
|
}))
|
|
166
|
-
};
|
|
215
|
+
});
|
|
167
216
|
}
|
|
168
217
|
// ============================================================================
|
|
169
218
|
// OpenAI / xAI Responses API continuation builder
|
|
@@ -176,17 +225,25 @@ function buildAnthropicContinuation(accBuffer, toolResults) {
|
|
|
176
225
|
* `function_call_output` items (the harness execution result), one pair
|
|
177
226
|
* per executed tool call.
|
|
178
227
|
*
|
|
228
|
+
* **Single source of truth for the id (id-correlation fix).** Each
|
|
229
|
+
* `function_call_output.call_id` is drawn from — and validated against — the
|
|
230
|
+
* set of `function_call.call_id`s in the accumulation map. It is NEVER derived
|
|
231
|
+
* from the tool name (the OpenAI parity of the Anthropic `tool_use_id` fix). A
|
|
232
|
+
* missing / empty / unmatched call id fails the build loud rather than emitting
|
|
233
|
+
* a continuation the provider would reject.
|
|
234
|
+
*
|
|
179
235
|
* @internal
|
|
180
236
|
*/
|
|
181
|
-
function buildOpenAiContinuation(calls, toolResults) {
|
|
182
|
-
var _a;
|
|
237
|
+
function buildOpenAiContinuation(calls, toolResults, logger) {
|
|
183
238
|
const items = [];
|
|
184
239
|
// Emit function_call items for each call (model's side). Per the Responses API spec
|
|
185
240
|
// (ResponseFunctionToolCall), `call_id` is the required correlation field — it must
|
|
186
241
|
// match the matching function_call_output's `call_id` below. The optional `id` field
|
|
187
242
|
// is the output-item id (`fc_*`) used to reference the streamed item; we omit it
|
|
188
243
|
// because it is not load-bearing for input items.
|
|
244
|
+
const bufferedCallIds = new Set();
|
|
189
245
|
for (const [callId, call] of calls) {
|
|
246
|
+
bufferedCallIds.add(callId);
|
|
190
247
|
items.push({
|
|
191
248
|
type: 'function_call',
|
|
192
249
|
call_id: callId,
|
|
@@ -194,15 +251,31 @@ function buildOpenAiContinuation(calls, toolResults) {
|
|
|
194
251
|
arguments: call.argsBuffer
|
|
195
252
|
});
|
|
196
253
|
}
|
|
197
|
-
// Emit function_call_output items (harness execution results).
|
|
254
|
+
// Emit function_call_output items (harness execution results). Correlate each
|
|
255
|
+
// to a buffered function_call call_id — never the tool name — and fail loud on
|
|
256
|
+
// a missing / empty / mismatched id.
|
|
198
257
|
for (const r of toolResults) {
|
|
258
|
+
if (!isUsableId(r.callId)) {
|
|
259
|
+
return (0, ts_utils_1.fail)(`OpenAI continuation: tool '${r.toolName}' result has no call id (missing or empty); ` +
|
|
260
|
+
`cannot correlate it to a function_call item`);
|
|
261
|
+
}
|
|
262
|
+
if (!bufferedCallIds.has(r.callId)) {
|
|
263
|
+
return (0, ts_utils_1.fail)(`OpenAI continuation: tool '${r.toolName}' call id '${r.callId}' does not match any ` +
|
|
264
|
+
`accumulated function_call call_id`);
|
|
265
|
+
}
|
|
199
266
|
items.push({
|
|
200
267
|
type: 'function_call_output',
|
|
201
|
-
call_id:
|
|
268
|
+
call_id: r.callId,
|
|
202
269
|
output: r.result
|
|
203
270
|
});
|
|
204
271
|
}
|
|
205
|
-
|
|
272
|
+
// Decisive diagnostic: the function_call.call_id ↔ function_call_output.call_id
|
|
273
|
+
// pairing for the outgoing continuation (matched by construction here).
|
|
274
|
+
if (logger) {
|
|
275
|
+
const pairing = toolResults.map((r) => `${r.toolName}:${r.callId}`).join(', ');
|
|
276
|
+
logger.detail(`ai-assist:openai-continuation function_call.call_id↔output.call_id [${pairing}]`);
|
|
277
|
+
}
|
|
278
|
+
return (0, ts_utils_1.succeed)({
|
|
206
279
|
messages: items,
|
|
207
280
|
toolCallsSummary: toolResults.map((r) => ({
|
|
208
281
|
toolName: r.toolName,
|
|
@@ -211,7 +284,7 @@ function buildOpenAiContinuation(calls, toolResults) {
|
|
|
211
284
|
result: r.result,
|
|
212
285
|
isError: r.isError
|
|
213
286
|
}))
|
|
214
|
-
};
|
|
287
|
+
});
|
|
215
288
|
}
|
|
216
289
|
// ============================================================================
|
|
217
290
|
// Gemini continuation builder
|
|
@@ -223,16 +296,23 @@ function buildOpenAiContinuation(calls, toolResults) {
|
|
|
223
296
|
* user turn with `functionResponse` parts (correlation by tool name, since
|
|
224
297
|
* Gemini does not assign call IDs).
|
|
225
298
|
*
|
|
299
|
+
* When thinking is enabled, Gemini stamps an opaque `thoughtSignature` on each
|
|
300
|
+
* `functionCall` part and requires it echoed back, verbatim, as a sibling of
|
|
301
|
+
* `functionCall` on the continuation's model turn — otherwise the follow-up is
|
|
302
|
+
* rejected with "Function call is missing a thought_signature in functionCall
|
|
303
|
+
* parts". The captured signature (see {@link IAccumulatedGeminiFunctionCall})
|
|
304
|
+
* is replayed here only when present; the key is omitted entirely when thinking
|
|
305
|
+
* was disabled. See https://ai.google.dev/gemini-api/docs/thought-signatures.
|
|
306
|
+
*
|
|
226
307
|
* @internal
|
|
227
308
|
*/
|
|
228
309
|
function buildGeminiContinuation(calls, toolResults) {
|
|
229
|
-
// Model turn: functionCall parts for each call.
|
|
230
|
-
|
|
231
|
-
|
|
310
|
+
// Model turn: functionCall parts for each call. Replay the part-level
|
|
311
|
+
// thoughtSignature as a sibling of functionCall only when present.
|
|
312
|
+
const modelParts = calls.map((call) => (Object.assign({ functionCall: {
|
|
232
313
|
name: call.name,
|
|
233
314
|
args: call.args
|
|
234
|
-
}
|
|
235
|
-
}));
|
|
315
|
+
} }, (call.thoughtSignature !== undefined ? { thoughtSignature: call.thoughtSignature } : {}))));
|
|
236
316
|
// User turn: functionResponse parts for each executed result.
|
|
237
317
|
// Correlation is by name since Gemini has no call IDs.
|
|
238
318
|
const userParts = toolResults.map((r) => ({
|
|
@@ -263,6 +343,20 @@ function buildGeminiContinuation(calls, toolResults) {
|
|
|
263
343
|
// ============================================================================
|
|
264
344
|
// executeClientToolTurn
|
|
265
345
|
// ============================================================================
|
|
346
|
+
/**
|
|
347
|
+
* True when a request would combine Gemini built-in grounding (a `web_search`
|
|
348
|
+
* server tool) with client (function) tools — a combination Gemini's
|
|
349
|
+
* `generateContent` API rejects with HTTP 400 (`INVALID_ARGUMENT`). Callers gate
|
|
350
|
+
* on `descriptor.apiFormat === 'gemini'` before consulting this; other providers
|
|
351
|
+
* accept the mix. Kept as a pure predicate so the conflict rule is unit-testable
|
|
352
|
+
* without a live stream.
|
|
353
|
+
*
|
|
354
|
+
* @internal
|
|
355
|
+
*/
|
|
356
|
+
function hasGeminiToolConflict(tools, clientTools) {
|
|
357
|
+
var _a;
|
|
358
|
+
return clientTools.length > 0 && ((_a = tools === null || tools === void 0 ? void 0 : tools.some((t) => t.type === 'web_search')) !== null && _a !== void 0 ? _a : false);
|
|
359
|
+
}
|
|
266
360
|
/**
|
|
267
361
|
* Orchestrates a single client-tool streaming turn for any supported provider.
|
|
268
362
|
*
|
|
@@ -285,7 +379,7 @@ function buildGeminiContinuation(calls, toolResults) {
|
|
|
285
379
|
* @public
|
|
286
380
|
*/
|
|
287
381
|
function executeClientToolTurn(params) {
|
|
288
|
-
const { descriptor, apiKey, system, messages, continuationMessages, temperature, tools, clientTools, signal, logger, resolvedThinking, model, endpoint } = params;
|
|
382
|
+
const { descriptor, apiKey, system, messages, continuationMessages, temperature, tools, clientTools, signal, logger, resolvedThinking, model, endpoint, onBeforeToolExecute } = params;
|
|
289
383
|
const splitResult = (0, chatRequestBuilders_1.splitChatRequest)(system, messages);
|
|
290
384
|
if (splitResult.isFailure()) {
|
|
291
385
|
return (0, ts_utils_1.fail)(splitResult.message);
|
|
@@ -308,11 +402,18 @@ function executeClientToolTurn(params) {
|
|
|
308
402
|
// This is the fix for P1-1: client tools were never sent to the provider because
|
|
309
403
|
// the adapters only received `tools` (server tools). Both must coexist per design §2.5.
|
|
310
404
|
const effectiveTools = clientTools.length > 0 ? [...(tools !== null && tools !== void 0 ? tools : []), ...clientTools.map((t) => t.config)] : tools;
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
405
|
+
// Gemini pre-flight: its generateContent API HTTP-400s (INVALID_ARGUMENT) when
|
|
406
|
+
// built-in grounding (`web_search`) and function calling (client tools) are
|
|
407
|
+
// combined in one request. Fail fast with a clear, actionable message rather
|
|
408
|
+
// than letting the opaque wire 400 surface. Other providers accept the mix.
|
|
409
|
+
if (descriptor.apiFormat === 'gemini' && hasGeminiToolConflict(tools, clientTools)) {
|
|
410
|
+
return (0, ts_utils_1.fail)('executeClientToolTurn: Gemini cannot combine web_search grounding with client (function) tools in the same request; send one or the other');
|
|
411
|
+
}
|
|
412
|
+
const modelResult = (0, model_1.resolveProviderModel)(descriptor, model);
|
|
413
|
+
if (modelResult.isFailure()) {
|
|
414
|
+
return (0, ts_utils_1.fail)(modelResult.message);
|
|
315
415
|
}
|
|
416
|
+
const resolvedModel = modelResult.value;
|
|
316
417
|
const baseUrlResult = (0, endpoint_1.resolveEffectiveBaseUrl)(descriptor, endpoint);
|
|
317
418
|
if (baseUrlResult.isFailure()) {
|
|
318
419
|
return (0, ts_utils_1.fail)(baseUrlResult.message);
|
|
@@ -332,13 +433,13 @@ function executeClientToolTurn(params) {
|
|
|
332
433
|
const streamPromise = (() => {
|
|
333
434
|
switch (descriptor.apiFormat) {
|
|
334
435
|
case 'anthropic':
|
|
335
|
-
return (0, anthropic_1.callAnthropicStream)(config, prompt, head,
|
|
436
|
+
return (0, anthropic_1.callAnthropicStream)(config, prompt, head, temperature, effectiveTools, logger, signal, resolvedThinking, anthropicBuffer, continuationMessages);
|
|
336
437
|
case 'openai':
|
|
337
438
|
return (0, openaiResponses_1.callOpenAiResponsesStream)(config, prompt,
|
|
338
439
|
/* c8 ignore next 1 - defensive: openai path requires tools; empty array fallback unreachable in practice */
|
|
339
|
-
effectiveTools !== null && effectiveTools !== void 0 ? effectiveTools : [], head,
|
|
440
|
+
effectiveTools !== null && effectiveTools !== void 0 ? effectiveTools : [], head, temperature, logger, signal, resolvedThinking, openAiCallMap, continuationMessages);
|
|
340
441
|
case 'gemini':
|
|
341
|
-
return (0, gemini_1.callGeminiStream)(config, prompt, head,
|
|
442
|
+
return (0, gemini_1.callGeminiStream)(config, prompt, head, temperature, effectiveTools, logger, signal, resolvedThinking, geminiCalls, continuationMessages);
|
|
342
443
|
/* c8 ignore next 4 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
343
444
|
default: {
|
|
344
445
|
const _exhaustive = descriptor.apiFormat;
|
|
@@ -424,6 +525,45 @@ function executeClientToolTurn(params) {
|
|
|
424
525
|
toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
|
|
425
526
|
continue;
|
|
426
527
|
}
|
|
528
|
+
// Host gate: run after arg-validation, before execute. A `deny` decision
|
|
529
|
+
// synthesizes a denial tool-result and continues the turn; a `fail`/reject
|
|
530
|
+
// from the gate itself is a hard error (mirrors an execute failure), never
|
|
531
|
+
// a silent deny.
|
|
532
|
+
if (onBeforeToolExecute !== undefined) {
|
|
533
|
+
const decisionResult = (yield __await((0, ts_utils_1.captureAsyncResult)(async () => onBeforeToolExecute(tool, validationResult.value)))).onSuccess((decision) => decision);
|
|
534
|
+
if (decisionResult.isFailure()) {
|
|
535
|
+
const errMsg = `${toolName} (callId=${callId}): ${decisionResult.message}`;
|
|
536
|
+
const resultEvent = {
|
|
537
|
+
type: 'client-tool-result',
|
|
538
|
+
toolName,
|
|
539
|
+
callId,
|
|
540
|
+
result: errMsg,
|
|
541
|
+
isError: true
|
|
542
|
+
};
|
|
543
|
+
yield yield __await(resultEvent);
|
|
544
|
+
// Gate-fail is a hard, turn-terminating error (unlike a non-fatal `deny`, whose
|
|
545
|
+
// generator continues). Emit an explicit `error` event so a consumer watching only
|
|
546
|
+
// `events` sees the fatal failure inline and can distinguish it from a deny — matching
|
|
547
|
+
// the stream-open / continuation hard-error paths.
|
|
548
|
+
yield yield __await({ type: 'error', message: errMsg });
|
|
549
|
+
toolResults.push({ toolName, callId, args, result: errMsg, isError: true });
|
|
550
|
+
resolveNextTurn((0, ts_utils_1.fail)(errMsg));
|
|
551
|
+
return yield __await(void 0);
|
|
552
|
+
}
|
|
553
|
+
if (decisionResult.value.action === 'deny') {
|
|
554
|
+
const denialMsg = `${toolName} (callId=${callId}): tool execution denied: ${decisionResult.value.reason}`;
|
|
555
|
+
const resultEvent = {
|
|
556
|
+
type: 'client-tool-result',
|
|
557
|
+
toolName,
|
|
558
|
+
callId,
|
|
559
|
+
result: denialMsg,
|
|
560
|
+
isError: true
|
|
561
|
+
};
|
|
562
|
+
yield yield __await(resultEvent);
|
|
563
|
+
toolResults.push({ toolName, callId, args, result: denialMsg, isError: true });
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
427
567
|
const executeResult = yield __await((0, ts_utils_1.captureAsyncResult)(async () => tool.execute(validationResult.value)));
|
|
428
568
|
const executionResult = executeResult.isSuccess()
|
|
429
569
|
? executeResult.value
|
|
@@ -505,16 +645,18 @@ function executeClientToolTurn(params) {
|
|
|
505
645
|
resolveNextTurn((0, ts_utils_1.succeed)({ continuation: undefined, truncated, fullText }));
|
|
506
646
|
return yield __await(void 0);
|
|
507
647
|
}
|
|
508
|
-
let
|
|
648
|
+
let continuationResult;
|
|
509
649
|
switch (descriptor.apiFormat) {
|
|
510
650
|
case 'anthropic':
|
|
511
|
-
|
|
651
|
+
continuationResult = buildAnthropicContinuation(anthropicBuffer, toolResults, logger);
|
|
512
652
|
break;
|
|
513
653
|
case 'openai':
|
|
514
|
-
|
|
654
|
+
continuationResult = buildOpenAiContinuation(openAiCallMap, toolResults, logger);
|
|
515
655
|
break;
|
|
516
656
|
case 'gemini':
|
|
517
|
-
|
|
657
|
+
// Gemini correlates by tool name by design (no call ids) — its builder
|
|
658
|
+
// cannot mis-key and stays non-fallible.
|
|
659
|
+
continuationResult = (0, ts_utils_1.succeed)(buildGeminiContinuation(geminiCalls, toolResults));
|
|
518
660
|
break;
|
|
519
661
|
/* c8 ignore next 5 - defensive coding: exhaustive switch guaranteed by TypeScript */
|
|
520
662
|
default: {
|
|
@@ -523,6 +665,16 @@ function executeClientToolTurn(params) {
|
|
|
523
665
|
return yield __await(void 0);
|
|
524
666
|
}
|
|
525
667
|
}
|
|
668
|
+
// A bad id-correlation fails loud here rather than emitting a malformed
|
|
669
|
+
// continuation the provider would reject as a "malformed identifier". Mirror
|
|
670
|
+
// the stream-open-failure path above: surface an `error` event so a consumer
|
|
671
|
+
// iterating `events` sees the failure inline, not only via `nextTurn`.
|
|
672
|
+
if (continuationResult.isFailure()) {
|
|
673
|
+
resolveNextTurn((0, ts_utils_1.fail)(continuationResult.message));
|
|
674
|
+
yield yield __await({ type: 'error', message: continuationResult.message });
|
|
675
|
+
return yield __await(void 0);
|
|
676
|
+
}
|
|
677
|
+
let continuation = continuationResult.value;
|
|
526
678
|
// Prepend inbound continuationMessages so the returned continuation is cumulative.
|
|
527
679
|
// A consumer that does `tail = outcome.continuation.messages` (replace) is then
|
|
528
680
|
// correct for all N rounds — messages always contains the full wire tail from
|