@opencode/ai 2.0.18 → 2.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -12
- package/dist/cache-policy.js +7 -0
- package/dist/experimental/evaluation.d.ts +7 -0
- package/dist/experimental/evaluation.js +2 -0
- package/dist/experimental/system-one.js +10 -7
- package/dist/generation.d.ts +1 -1
- package/dist/generation.js +23 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/llm.d.ts +1 -1
- package/dist/promise.d.ts +3 -3
- package/dist/promise.js +4 -3
- package/dist/protocols/alibaba-chat.d.ts +8 -2
- package/dist/protocols/alibaba-responses.d.ts +14 -14
- package/dist/protocols/anthropic-messages.js +2 -15
- package/dist/protocols/bedrock-converse.d.ts +1 -1
- package/dist/protocols/bedrock-converse.js +38 -12
- package/dist/protocols/deepgram-speech.js +9 -7
- package/dist/protocols/deepgram-transcription.js +4 -10
- package/dist/protocols/elevenlabs-transcription.d.ts +28 -0
- package/dist/protocols/elevenlabs-transcription.js +137 -0
- package/dist/protocols/gemini.js +1 -10
- package/dist/protocols/google-speech.js +9 -2
- package/dist/protocols/google-transcription.js +4 -0
- package/dist/protocols/google-video.js +11 -2
- package/dist/protocols/meta-messages.d.ts +5 -1
- package/dist/protocols/meta-messages.js +11 -4
- package/dist/protocols/meta-responses.d.ts +16 -16
- package/dist/protocols/mistral-chat.d.ts +6 -0
- package/dist/protocols/mistral-chat.js +8 -5
- package/dist/protocols/open-responses.d.ts +31 -31
- package/dist/protocols/openai-chat.d.ts +53 -10
- package/dist/protocols/openai-chat.js +21 -8
- package/dist/protocols/openai-compatible-chat.d.ts +7 -2
- package/dist/protocols/openai-compatible-responses.d.ts +2 -2
- package/dist/protocols/openai-responses.d.ts +22 -22
- package/dist/protocols/openai-speech.js +6 -1
- package/dist/protocols/openai-transcription.js +1 -1
- package/dist/protocols/runway-video.js +2 -1
- package/dist/protocols/utils/claude-model.d.ts +8 -0
- package/dist/protocols/utils/claude-model.js +15 -0
- package/dist/protocols/utils/gemini-generate-content.d.ts +6 -0
- package/dist/protocols/utils/gemini-generate-content.js +33 -0
- package/dist/protocols/utils/media-input.d.ts +4 -3
- package/dist/protocols/utils/media-input.js +5 -4
- package/dist/protocols/utils/speaker-turns.d.ts +3 -0
- package/dist/protocols/utils/speaker-turns.js +9 -0
- package/dist/protocols/utils/speech-stream.d.ts +1 -0
- package/dist/protocols/utils/speech-stream.js +1 -0
- package/dist/protocols/utils/tool-stream.d.ts +4 -3
- package/dist/protocols/utils/tool-stream.js +1 -1
- package/dist/protocols/xai-responses.d.ts +14 -14
- package/dist/protocols/xai-video.js +7 -1
- package/dist/protocols/zai-chat.d.ts +8 -2
- package/dist/provider-error.d.ts +6 -0
- package/dist/provider-error.js +65 -2
- package/dist/providers/alibaba.d.ts +9 -4
- package/dist/providers/amazon-bedrock-mantle.d.ts +9 -4
- package/dist/providers/anthropic-compatible.js +3 -2
- package/dist/providers/azure.d.ts +11 -6
- package/dist/providers/baseten.d.ts +14 -4
- package/dist/providers/cerebras.d.ts +14 -4
- package/dist/providers/cloudflare-ai-gateway.d.ts +18 -8
- package/dist/providers/cloudflare-workers-ai.d.ts +14 -4
- package/dist/providers/deepinfra.d.ts +14 -4
- package/dist/providers/deepseek.d.ts +14 -4
- package/dist/providers/elevenlabs.d.ts +5 -0
- package/dist/providers/elevenlabs.js +4 -0
- package/dist/providers/fireworks.d.ts +14 -4
- package/dist/providers/google-vertex-chat.d.ts +7 -2
- package/dist/providers/google-vertex-responses.d.ts +2 -2
- package/dist/providers/groq.d.ts +15 -4
- package/dist/providers/meta.d.ts +14 -5
- package/dist/providers/minimax.d.ts +9 -4
- package/dist/providers/moonshot.d.ts +9 -4
- package/dist/providers/openai-compatible-responses.d.ts +2 -2
- package/dist/providers/openai-compatible.d.ts +7 -2
- package/dist/providers/openai.d.ts +9 -4
- package/dist/providers/openrouter.d.ts +27 -6
- package/dist/providers/togetherai.d.ts +14 -4
- package/dist/providers/xai.d.ts +7 -2
- package/dist/providers/xai.js +3 -3
- package/dist/providers/zai-coding-plan.d.ts +9 -4
- package/dist/providers/zai.d.ts +7 -2
- package/dist/route/client.d.ts +1 -1
- package/dist/route/executor.js +12 -8
- package/dist/route/media-protocol.d.ts +13 -3
- package/dist/route/media-protocol.js +16 -6
- package/dist/route/media.js +18 -4
- package/dist/schema/events.d.ts +30 -30
- package/dist/testing.d.ts +8 -8
- package/dist/transcription.d.ts +1 -1
- package/dist/transcription.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -129,8 +129,9 @@ VercelAIGateway.configure().experimental.evaluation("typesafe-ai/jev")
|
|
|
129
129
|
|
|
130
130
|
OpenRouter reads `OPENROUTER_API_KEY`. Vercel reads `AI_GATEWAY_API_KEY`, then `VERCEL_OIDC_TOKEN`.
|
|
131
131
|
The common API uses `boolean`; System One routes lower it to native `noul`.
|
|
132
|
-
Choice and score
|
|
133
|
-
|
|
132
|
+
Choice and score answers include `confidence` when the provider returns it, such as
|
|
133
|
+
`response.answers.department.confidence`. Score legends remain available in provider metadata, and
|
|
134
|
+
the provider's rounded probabilities are returned unchanged.
|
|
134
135
|
|
|
135
136
|
## Alibaba Cloud Model Studio
|
|
136
137
|
|
|
@@ -752,7 +753,10 @@ const events = Video.stream({ model: Runway.configure({ apiKey }).video("gen4.5"
|
|
|
752
753
|
|
|
753
754
|
Status polls, result fetches, cancels, and asset downloads all run through the same request executor with the route's
|
|
754
755
|
auth. `Generation.await` and `Generation.events` fail with a
|
|
755
|
-
`Timeout` reason when `poll.timeout` (default 10 minutes) elapses.
|
|
756
|
+
`Timeout` reason when `poll.timeout` (default 10 minutes) elapses. Status polls and result fetches retry transient
|
|
757
|
+
failures (rate limits, provider 5xx, network errors) with backoff that honors `retry-after`, always within
|
|
758
|
+
`poll.timeout`; submits and cancels never retry. Interrupting a wait (or aborting its `signal`) does not cancel the
|
|
759
|
+
provider job, which keeps running and billing: call `cancel()` to stop it. Failed,
|
|
756
760
|
cancelled, and expired generations fail typed with the provider's terminal document on `reason.body`; moderation
|
|
757
761
|
outcomes (Veo `raiMediaFilteredReasons`, xAI `respect_moderation`, Runway `SAFETY.*` codes) surface as `notices` when
|
|
758
762
|
a video is still returned and as a `ContentPolicy` reason when nothing is.
|
|
@@ -773,7 +777,9 @@ Provider notes:
|
|
|
773
777
|
The promise client exposes the same surface: `ai.video.start(...)` resolves to a handle with `await`, `events`,
|
|
774
778
|
`result`, `refresh`, `cancel`, and `token`; `ai.video.generate`, `ai.video.resume(model, token)`, and
|
|
775
779
|
`ai.video.stream` mirror the Effect API. The handle's `status` and `progress` are a snapshot from when it was
|
|
776
|
-
created; `refresh()` resolves to a new handle.
|
|
780
|
+
created; `refresh()` resolves to a new handle. Every promise method and stream accepts `{ signal }`: like `fetch`,
|
|
781
|
+
aborting rejects the Promise or throws from the `for await` loop with `signal.reason` (an `AbortError` `DOMException`
|
|
782
|
+
unless `abort(reason)` passed one), while `break` stops a stream without throwing.
|
|
777
783
|
|
|
778
784
|
```ts
|
|
779
785
|
import { ai } from "@opencode/ai/promise"
|
|
@@ -871,11 +877,12 @@ for await (const event of ai.speech.stream({ model, text: "Hello from OpenCode."
|
|
|
871
877
|
## Transcription
|
|
872
878
|
|
|
873
879
|
Transcription (speech-to-text) is the one modality whose providers use every route kind: OpenAI and Gemini stream,
|
|
874
|
-
Deepgram
|
|
875
|
-
them; `Transcription.start` / `resume` return a `Generation` on queued routes and fail with
|
|
876
|
-
elsewhere. Models come from `.transcription(...)` selectors on the `OpenAI`, `Google`,
|
|
877
|
-
facades. Common fields (`language`, `prompt`,
|
|
878
|
-
natively or fail with a typed `AIError` before
|
|
880
|
+
Deepgram and ElevenLabs answer inline, and AssemblyAI is queued. `Transcription.generate` and `Transcription.stream`
|
|
881
|
+
work on all of them; `Transcription.start` / `resume` return a `Generation` on queued routes and fail with
|
|
882
|
+
`UnsupportedOperation` elsewhere. Models come from `.transcription(...)` selectors on the `OpenAI`, `Google`,
|
|
883
|
+
`Deepgram`, `ElevenLabs`, and `AssemblyAI` facades. Common fields (`language`, `prompt`,
|
|
884
|
+
`timestamps: "none" | "segment" | "word"`, `diarize`, `speakers`) lower natively or fail with a typed `AIError` before
|
|
885
|
+
any network call; a route may return more than asked.
|
|
879
886
|
|
|
880
887
|
```ts
|
|
881
888
|
import { Console, Effect, Stream } from "effect"
|
|
@@ -887,7 +894,7 @@ const openai = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY })
|
|
|
887
894
|
const program = Effect.gen(function* () {
|
|
888
895
|
const audio = yield* Media.file("./call.mp3")
|
|
889
896
|
|
|
890
|
-
// Speaker-labelled segments; labels are provider-native strings ("A", "0", "spk:0").
|
|
897
|
+
// Speaker-labelled segments; labels are provider-native strings ("A", "0", "spk:0", "speaker_0").
|
|
891
898
|
const response = yield* Transcription.generate({
|
|
892
899
|
model: Deepgram.configure({ apiKey }).transcription("nova-3"),
|
|
893
900
|
audio,
|
|
@@ -897,7 +904,7 @@ const program = Effect.gen(function* () {
|
|
|
897
904
|
response.text // "Hello from OpenCode."
|
|
898
905
|
response.segments // [{ text, startSeconds, endSeconds, speaker: "0" }]
|
|
899
906
|
response.words // [{ text, startSeconds, endSeconds, speaker, confidence }]
|
|
900
|
-
response.language // the provider's own value, lowercased ("en", "english", "en_us")
|
|
907
|
+
response.language // the provider's own value, lowercased ("en", "eng", "english", "en_us")
|
|
901
908
|
|
|
902
909
|
// Text deltas as the model transcribes, then one finish carrying the whole transcript.
|
|
903
910
|
yield* Transcription.stream({ model: openai.transcription("gpt-4o-mini-transcribe"), audio }).pipe(
|
|
@@ -921,7 +928,12 @@ Provider notes:
|
|
|
921
928
|
- **OpenAI** takes inline audio only; `diarize` needs `gpt-4o-transcribe-diarize`, timestamps need `whisper-1`, and `whisper-1` does not stream.
|
|
922
929
|
- **Gemini** needs a transcribe model (`gemini-3.5-transcribe`); `prompt` and `speakers` fail typed.
|
|
923
930
|
- **Deepgram** detects the language unless `language` is set; vocabulary goes in `providerOptions.keyterm`.
|
|
924
|
-
- **
|
|
931
|
+
- **ElevenLabs** (`scribe_v2`) uploads inline audio as the multipart `file` and sends a URL as `source_url`. Words
|
|
932
|
+
always carry timestamps, and segments are speaker turns, so `diarize`, `timestamps: "segment"`, or `speakers` turns
|
|
933
|
+
on diarization. `speakers` is an upper bound (`num_speakers`); `prompt` fails typed (vocabulary goes in
|
|
934
|
+
`providerOptions.keyterms`), as do webhook delivery and per-channel output (`use_multi_channel` without
|
|
935
|
+
`multichannel_output_style: "combined"`).
|
|
936
|
+
- **AssemblyAI** uploads inline audio before submitting and treats `speakers` as the exact speaker count.
|
|
925
937
|
|
|
926
938
|
The promise client mirrors the Effect API:
|
|
927
939
|
|
package/dist/cache-policy.js
CHANGED
|
@@ -36,8 +36,15 @@ const resolve = (policy) => {
|
|
|
36
36
|
// prefix caching, Gemini's implicit + out-of-band CachedContent). Skip the
|
|
37
37
|
// whole policy pass for these — emitting hints would be harmless but pointless.
|
|
38
38
|
const RESPECTS_INLINE_HINTS = new Set([
|
|
39
|
+
"alibaba-messages",
|
|
39
40
|
"anthropic-messages",
|
|
41
|
+
"anthropic-compatible-messages",
|
|
42
|
+
"cloudflare-ai-gateway-messages",
|
|
40
43
|
"google-vertex-messages",
|
|
44
|
+
"meta-messages",
|
|
45
|
+
"minimax-messages",
|
|
46
|
+
"moonshot-messages",
|
|
47
|
+
"zai-coding-messages",
|
|
41
48
|
"bedrock-converse",
|
|
42
49
|
"openrouter",
|
|
43
50
|
]);
|
|
@@ -54,12 +54,14 @@ export declare const ChoiceAnswer: Schema.Struct<{
|
|
|
54
54
|
readonly type: Schema.Literal<"choice">;
|
|
55
55
|
readonly choice: Schema.String;
|
|
56
56
|
readonly probabilities: Schema.optional<Schema.$Record<Schema.String, Schema.Number>>;
|
|
57
|
+
readonly confidence: Schema.optional<Schema.Number>;
|
|
57
58
|
}>;
|
|
58
59
|
export type ChoiceAnswer = Schema.Schema.Type<typeof ChoiceAnswer>;
|
|
59
60
|
export declare const ScoreAnswer: Schema.Struct<{
|
|
60
61
|
readonly type: Schema.Literal<"score">;
|
|
61
62
|
readonly score: Schema.Number;
|
|
62
63
|
readonly probabilities: Schema.optional<Schema.$Record<Schema.String, Schema.Number>>;
|
|
64
|
+
readonly confidence: Schema.optional<Schema.Number>;
|
|
63
65
|
}>;
|
|
64
66
|
export type ScoreAnswer = Schema.Schema.Type<typeof ScoreAnswer>;
|
|
65
67
|
export declare const BooleanAnswer: Schema.Struct<{
|
|
@@ -71,10 +73,12 @@ export declare const EvaluationAnswer: Schema.toTaggedUnion<"type", readonly [Sc
|
|
|
71
73
|
readonly type: Schema.Literal<"choice">;
|
|
72
74
|
readonly choice: Schema.String;
|
|
73
75
|
readonly probabilities: Schema.optional<Schema.$Record<Schema.String, Schema.Number>>;
|
|
76
|
+
readonly confidence: Schema.optional<Schema.Number>;
|
|
74
77
|
}>, Schema.Struct<{
|
|
75
78
|
readonly type: Schema.Literal<"score">;
|
|
76
79
|
readonly score: Schema.Number;
|
|
77
80
|
readonly probabilities: Schema.optional<Schema.$Record<Schema.String, Schema.Number>>;
|
|
81
|
+
readonly confidence: Schema.optional<Schema.Number>;
|
|
78
82
|
}>, Schema.Struct<{
|
|
79
83
|
readonly type: Schema.Literal<"boolean">;
|
|
80
84
|
readonly probability: Schema.Number;
|
|
@@ -87,6 +91,7 @@ export type AnswerFor<Question extends EvaluationQuestion> = Question extends {
|
|
|
87
91
|
readonly type: "choice";
|
|
88
92
|
readonly choice: Extract<keyof Criteria, string>;
|
|
89
93
|
readonly probabilities?: Readonly<Record<Extract<keyof Criteria, string>, number>>;
|
|
94
|
+
readonly confidence?: number;
|
|
90
95
|
} : Question extends {
|
|
91
96
|
readonly type: "score";
|
|
92
97
|
} ? ScoreAnswer : BooleanAnswer;
|
|
@@ -206,10 +211,12 @@ declare const EvaluationResponse_base: Schema.Class<EvaluationResponse, Schema.S
|
|
|
206
211
|
readonly type: Schema.Literal<"choice">;
|
|
207
212
|
readonly choice: Schema.String;
|
|
208
213
|
readonly probabilities: Schema.optional<Schema.$Record<Schema.String, Schema.Number>>;
|
|
214
|
+
readonly confidence: Schema.optional<Schema.Number>;
|
|
209
215
|
}>, Schema.Struct<{
|
|
210
216
|
readonly type: Schema.Literal<"score">;
|
|
211
217
|
readonly score: Schema.Number;
|
|
212
218
|
readonly probabilities: Schema.optional<Schema.$Record<Schema.String, Schema.Number>>;
|
|
219
|
+
readonly confidence: Schema.optional<Schema.Number>;
|
|
213
220
|
}>, Schema.Struct<{
|
|
214
221
|
readonly type: Schema.Literal<"boolean">;
|
|
215
222
|
readonly probability: Schema.Number;
|
|
@@ -33,11 +33,13 @@ export const ChoiceAnswer = Schema.Struct({
|
|
|
33
33
|
type: Schema.Literal("choice"),
|
|
34
34
|
choice: Schema.String,
|
|
35
35
|
probabilities: Schema.optional(Schema.Record(Schema.String, Probability)),
|
|
36
|
+
confidence: Schema.optional(Probability),
|
|
36
37
|
});
|
|
37
38
|
export const ScoreAnswer = Schema.Struct({
|
|
38
39
|
type: Schema.Literal("score"),
|
|
39
40
|
score: Schema.Number,
|
|
40
41
|
probabilities: Schema.optional(Schema.Record(Schema.String, Probability)),
|
|
42
|
+
confidence: Schema.optional(Probability),
|
|
41
43
|
});
|
|
42
44
|
export const BooleanAnswer = Schema.Struct({
|
|
43
45
|
type: Schema.Literal("boolean"),
|
|
@@ -80,34 +80,37 @@ export const model = (cfg) => EvaluationModel.make({
|
|
|
80
80
|
const fail = (message, cause, body) => new AIError({ reason: new InvalidProviderOutputError({ route: "system-one", message, body, http, cause }) });
|
|
81
81
|
const text = yield* res.text.pipe(Effect.mapError((cause) => fail("Failed to read the System One response", cause)));
|
|
82
82
|
const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Response))(text).pipe(Effect.mapError((cause) => fail("System One returned an invalid response", cause, text)));
|
|
83
|
-
const confidence = {};
|
|
84
83
|
const legend = {};
|
|
85
84
|
const answers = Object.fromEntries(Object.entries(data.answers).map(([id, answer]) => {
|
|
86
85
|
if (answer.type === "noul")
|
|
87
86
|
return [id, { type: "boolean", probability: answer.noul }];
|
|
88
87
|
if (answer.type === "choice") {
|
|
89
|
-
if (answer.confidence !== undefined)
|
|
90
|
-
confidence[id] = answer.confidence;
|
|
91
88
|
return [
|
|
92
89
|
id,
|
|
93
90
|
{
|
|
94
91
|
type: "choice",
|
|
95
92
|
choice: answer.choice,
|
|
96
93
|
probabilities: answer.probabilities,
|
|
94
|
+
...(answer.confidence === undefined ? {} : { confidence: answer.confidence }),
|
|
97
95
|
},
|
|
98
96
|
];
|
|
99
97
|
}
|
|
100
|
-
if (answer.confidence !== undefined)
|
|
101
|
-
confidence[id] = answer.confidence;
|
|
102
98
|
if (answer.legend !== undefined)
|
|
103
99
|
legend[id] = answer.legend;
|
|
104
|
-
return [
|
|
100
|
+
return [
|
|
101
|
+
id,
|
|
102
|
+
{
|
|
103
|
+
type: "score",
|
|
104
|
+
score: answer.score,
|
|
105
|
+
probabilities: answer.probabilities,
|
|
106
|
+
...(answer.confidence === undefined ? {} : { confidence: answer.confidence }),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
105
109
|
}));
|
|
106
110
|
const meta = {
|
|
107
111
|
...(data.id === undefined ? {} : { responseId: data.id }),
|
|
108
112
|
...(data.provider === undefined ? {} : { provider: data.provider }),
|
|
109
113
|
...data.provider_metadata?.[cfg.providerMetadataKey],
|
|
110
|
-
...(Object.keys(confidence).length === 0 ? {} : { confidence }),
|
|
111
114
|
...(Object.keys(legend).length === 0 ? {} : { legend }),
|
|
112
115
|
};
|
|
113
116
|
return new EvaluationResponse({
|
package/dist/generation.d.ts
CHANGED
|
@@ -72,8 +72,8 @@ export declare class Generation<Response> {
|
|
|
72
72
|
*/
|
|
73
73
|
events(options?: AwaitOptions): Stream.Stream<Event, AIError>;
|
|
74
74
|
private event;
|
|
75
|
-
private timeoutError;
|
|
76
75
|
private poll;
|
|
77
76
|
private schedule;
|
|
78
77
|
}
|
|
78
|
+
/** `events` followed by the expanded result, with the result fetch bounded by the same `poll.timeout` deadline. */
|
|
79
79
|
export declare const resultEvents: <Response, A>(generation: Generation<Response>, expand: (response: Response) => ReadonlyArray<A>, options?: AwaitOptions) => Stream.Stream<Observation | A, AIError>;
|
package/dist/generation.js
CHANGED
|
@@ -56,7 +56,7 @@ export class Generation {
|
|
|
56
56
|
const settled = this.terminal ? Effect.succeed(this) : this.poll(options?.poll);
|
|
57
57
|
return settled.pipe(
|
|
58
58
|
// Non-completed terminal states also go through `result` so the route can surface its provider failure body.
|
|
59
|
-
Effect.flatMap((generation) => generation.result()), Effect.timeoutOrElse({ duration: timeout, orElse: () => this.
|
|
59
|
+
Effect.flatMap((generation) => generation.result()), Effect.timeoutOrElse({ duration: timeout, orElse: () => timeoutError(this.id, timeout) }));
|
|
60
60
|
}
|
|
61
61
|
cancel() {
|
|
62
62
|
return this.route.cancel ?? Effect.void;
|
|
@@ -73,14 +73,7 @@ export class Generation {
|
|
|
73
73
|
const timeout = Duration.fromInputUnsafe(options?.poll?.timeout ?? DEFAULT_POLL_TIMEOUT);
|
|
74
74
|
return Stream.unwrap(Clock.currentTimeMillis.pipe(Effect.map((start) => {
|
|
75
75
|
const deadline = start + Duration.toMillis(timeout);
|
|
76
|
-
|
|
77
|
-
// race and schedule another zero-delay poll.
|
|
78
|
-
const refresh = Clock.currentTimeMillis.pipe(Effect.flatMap((now) => now >= deadline
|
|
79
|
-
? this.timeoutError(timeout)
|
|
80
|
-
: this.refresh().pipe(Effect.timeoutOrElse({
|
|
81
|
-
duration: Duration.millis(deadline - now),
|
|
82
|
-
orElse: () => this.timeoutError(timeout),
|
|
83
|
-
}))));
|
|
76
|
+
const refresh = within(this.refresh(), this.id, timeout, deadline);
|
|
84
77
|
const schedule = this.schedule(options?.poll).pipe(Schedule.modifyDelay((meta) => Effect.succeed(Duration.min(meta.duration, Duration.millis(Math.max(0, deadline - meta.now))))));
|
|
85
78
|
return Stream.fromEffectSchedule(refresh, schedule).pipe(Stream.takeUntil((generation) => generation.terminal), Stream.map((generation) => generation.event()));
|
|
86
79
|
})));
|
|
@@ -92,14 +85,6 @@ export class Generation {
|
|
|
92
85
|
return { type: "generation-queued", id: this.id, position: this.position };
|
|
93
86
|
return { type: "generation-progress", id: this.id, progress: this.progress };
|
|
94
87
|
}
|
|
95
|
-
timeoutError(timeout) {
|
|
96
|
-
return new AIError({
|
|
97
|
-
reason: new TimeoutError({
|
|
98
|
-
message: `Generation ${this.id} did not finish within ${Duration.format(timeout)}`,
|
|
99
|
-
timeoutMs: Duration.toMillis(timeout),
|
|
100
|
-
}),
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
88
|
poll(poll) {
|
|
104
89
|
return this.refresh().pipe(Effect.repeat({ schedule: this.schedule(poll), until: (generation) => generation.terminal }));
|
|
105
90
|
}
|
|
@@ -107,4 +92,24 @@ export class Generation {
|
|
|
107
92
|
return Schedule.spaced(poll?.interval ?? DEFAULT_POLL_INTERVAL);
|
|
108
93
|
}
|
|
109
94
|
}
|
|
110
|
-
|
|
95
|
+
/** `events` followed by the expanded result, with the result fetch bounded by the same `poll.timeout` deadline. */
|
|
96
|
+
export const resultEvents = (generation, expand, options) => {
|
|
97
|
+
const timeout = Duration.fromInputUnsafe(options?.poll?.timeout ?? DEFAULT_POLL_TIMEOUT);
|
|
98
|
+
return Stream.unwrap(Clock.currentTimeMillis.pipe(Effect.map((start) => generation.events(options).pipe(Stream.filter((event) => event.type !== "generation-finished"), Stream.concat(Stream.fromIterableEffect(within(generation.result(), generation.id, timeout, start + Duration.toMillis(timeout)).pipe(Effect.map(expand))))))));
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Run `effect` within the time left until `deadline`. Fails before starting once the deadline has passed: a fast
|
|
102
|
+
* request could otherwise win the zero-budget race and schedule another zero-delay poll.
|
|
103
|
+
*/
|
|
104
|
+
const within = (effect, id, timeout, deadline) => Clock.currentTimeMillis.pipe(Effect.flatMap((now) => now >= deadline
|
|
105
|
+
? Effect.fail(timeoutError(id, timeout))
|
|
106
|
+
: effect.pipe(Effect.timeoutOrElse({
|
|
107
|
+
duration: Duration.millis(deadline - now),
|
|
108
|
+
orElse: () => Effect.fail(timeoutError(id, timeout)),
|
|
109
|
+
}))));
|
|
110
|
+
const timeoutError = (id, timeout) => new AIError({
|
|
111
|
+
reason: new TimeoutError({
|
|
112
|
+
message: `Generation ${id} did not finish within ${Duration.format(timeout)}`,
|
|
113
|
+
timeoutMs: Duration.toMillis(timeout),
|
|
114
|
+
}),
|
|
115
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { ImageClient } from "./image-client.js";
|
|
|
4
4
|
export { Auth } from "./route/auth.js";
|
|
5
5
|
export { Provider } from "./provider.js";
|
|
6
6
|
export { ProviderPackage } from "./provider-package.js";
|
|
7
|
-
export { isContextOverflow, isContextOverflowFailure } from "./provider-error.js";
|
|
7
|
+
export { isContextOverflow, isContextOverflowFailure, isRetryable } from "./provider-error.js";
|
|
8
8
|
export type { RouteLanguageModelInput, RouteRoutedLanguageModelInput, Interface as LLMClientShape, LLMClientService, } from "./route/client.js";
|
|
9
9
|
export * from "./schema/index.js";
|
|
10
10
|
export { ImageAspectRatio, ImageEvent, ImageModel, ImageModelSchema, ImageRequest, ImageResponse, ImageSize, } from "./image.js";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { ImageClient } from "./image-client.js";
|
|
|
4
4
|
export { Auth } from "./route/auth.js";
|
|
5
5
|
export { Provider } from "./provider.js";
|
|
6
6
|
export { ProviderPackage } from "./provider-package.js";
|
|
7
|
-
export { isContextOverflow, isContextOverflowFailure } from "./provider-error.js";
|
|
7
|
+
export { isContextOverflow, isContextOverflowFailure, isRetryable } from "./provider-error.js";
|
|
8
8
|
export * from "./schema/index.js";
|
|
9
9
|
export { ImageAspectRatio, ImageEvent, ImageModel, ImageModelSchema, ImageRequest, ImageResponse, ImageSize, } from "./image.js";
|
|
10
10
|
export { Image } from "./image.js";
|
package/dist/llm.d.ts
CHANGED
|
@@ -184,8 +184,8 @@ export declare class GenerateObjectResponse<T> {
|
|
|
184
184
|
} | {
|
|
185
185
|
readonly id: string;
|
|
186
186
|
readonly type: "tool-error";
|
|
187
|
-
readonly name: string;
|
|
188
187
|
readonly message: string;
|
|
188
|
+
readonly name: string;
|
|
189
189
|
readonly error?: unknown;
|
|
190
190
|
readonly providerMetadata?: {
|
|
191
191
|
readonly [x: string]: {
|
package/dist/promise.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type GenerationHandle<Response> = Snapshot & {
|
|
|
30
30
|
/** Serializable JSON; pass it back to `resume` from another process. */
|
|
31
31
|
readonly token: unknown;
|
|
32
32
|
readonly await: (options?: AwaitOptions & RunOptions) => Promise<Response>;
|
|
33
|
-
/** Status observations until the first terminal one, polling like `await`; abort
|
|
33
|
+
/** Status observations until the first terminal one, polling like `await`; abort throws `signal.reason`. */
|
|
34
34
|
readonly events: (options?: AwaitOptions & RunOptions) => AsyncIterable<Event>;
|
|
35
35
|
/** The result without polling; fails when the generation has not completed. */
|
|
36
36
|
readonly result: (options?: RunOptions) => Promise<Response>;
|
|
@@ -211,8 +211,8 @@ export declare const make: (options?: Options) => {
|
|
|
211
211
|
} | {
|
|
212
212
|
readonly id: string;
|
|
213
213
|
readonly type: "tool-error";
|
|
214
|
-
readonly name: string;
|
|
215
214
|
readonly message: string;
|
|
215
|
+
readonly name: string;
|
|
216
216
|
readonly error?: unknown;
|
|
217
217
|
readonly providerMetadata?: {
|
|
218
218
|
readonly [x: string]: {
|
|
@@ -698,8 +698,8 @@ export declare const ai: {
|
|
|
698
698
|
} | {
|
|
699
699
|
readonly id: string;
|
|
700
700
|
readonly type: "tool-error";
|
|
701
|
-
readonly name: string;
|
|
702
701
|
readonly message: string;
|
|
702
|
+
readonly name: string;
|
|
703
703
|
readonly error?: unknown;
|
|
704
704
|
readonly providerMetadata?: {
|
|
705
705
|
readonly [x: string]: {
|
package/dist/promise.js
CHANGED
|
@@ -10,21 +10,22 @@ import { Speech } from "./speech.js";
|
|
|
10
10
|
import { Transcription, } from "./transcription.js";
|
|
11
11
|
import { fileMediaType } from "./utils/media-type.js";
|
|
12
12
|
import { Video } from "./video.js";
|
|
13
|
+
// Fails with `signal.reason` so aborted calls reject and aborted streams throw like `fetch`: an `AbortError` by default.
|
|
13
14
|
const abortEffect = (signal) => signal === undefined
|
|
14
15
|
? Effect.never
|
|
15
16
|
: Effect.callback((resume) => {
|
|
16
17
|
if (signal.aborted) {
|
|
17
|
-
resume(Effect.
|
|
18
|
+
resume(Effect.fail(signal.reason));
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
|
-
const onAbort = () => resume(Effect.
|
|
21
|
+
const onAbort = () => resume(Effect.fail(signal.reason));
|
|
21
22
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
22
23
|
return Effect.sync(() => signal.removeEventListener("abort", onAbort));
|
|
23
24
|
});
|
|
24
25
|
export const make = (options = {}) => {
|
|
25
26
|
const runtime = ManagedRuntime.make(AIClient.layerWith(options.layer ?? RequestExecutor.fetchLayer));
|
|
26
27
|
/** Run any package Effect (for example `LLMClient.compact(...)`) inside this runtime. */
|
|
27
|
-
const run = (effect, options) => runtime.runPromise(effect,
|
|
28
|
+
const run = (effect, options) => runtime.runPromise(Effect.raceFirst(effect, abortEffect(options?.signal)));
|
|
28
29
|
const iterate = (stream, options) => Stream.toAsyncIterable(Stream.unwrap(runtime.contextEffect.pipe(Effect.map((context) => stream.pipe(Stream.interruptWhen(abortEffect(options?.signal)), Stream.provideContext(context))))));
|
|
29
30
|
const handle = (generation) => ({
|
|
30
31
|
...generation.snapshot,
|
|
@@ -90,12 +90,17 @@ export declare const protocol: Protocol<{
|
|
|
90
90
|
readonly ttl?: string | undefined;
|
|
91
91
|
} | undefined;
|
|
92
92
|
readonly tool_calls?: readonly {
|
|
93
|
-
readonly id: string;
|
|
94
|
-
readonly type: "function";
|
|
95
93
|
readonly function: {
|
|
96
94
|
readonly name: string;
|
|
97
95
|
readonly arguments: string;
|
|
98
96
|
};
|
|
97
|
+
readonly id: string;
|
|
98
|
+
readonly type: "function";
|
|
99
|
+
readonly extra_content?: {
|
|
100
|
+
readonly google: {
|
|
101
|
+
readonly thought_signature: string;
|
|
102
|
+
};
|
|
103
|
+
} | undefined;
|
|
99
104
|
}[] | undefined;
|
|
100
105
|
readonly reasoning_details?: unknown;
|
|
101
106
|
} | {
|
|
@@ -208,6 +213,7 @@ export declare const protocol: Protocol<{
|
|
|
208
213
|
} | null | undefined;
|
|
209
214
|
readonly id?: string | null | undefined;
|
|
210
215
|
readonly index?: number | null | undefined;
|
|
216
|
+
readonly extra_content?: unknown;
|
|
211
217
|
}[] | null | undefined;
|
|
212
218
|
readonly reasoning_details?: unknown;
|
|
213
219
|
} | null | undefined;
|
|
@@ -77,8 +77,8 @@ export declare const protocol: Protocol<{
|
|
|
77
77
|
} | {
|
|
78
78
|
readonly type: "input_file";
|
|
79
79
|
readonly filename: string;
|
|
80
|
-
readonly file_data?: string | undefined;
|
|
81
80
|
readonly detail?: string | undefined;
|
|
81
|
+
readonly file_data?: string | undefined;
|
|
82
82
|
readonly file_url?: string | undefined;
|
|
83
83
|
} | {
|
|
84
84
|
readonly type: "input_text";
|
|
@@ -114,8 +114,8 @@ export declare const protocol: Protocol<{
|
|
|
114
114
|
} | {
|
|
115
115
|
readonly type: "input_file";
|
|
116
116
|
readonly filename: string;
|
|
117
|
-
readonly file_data?: string | undefined;
|
|
118
117
|
readonly detail?: string | undefined;
|
|
118
|
+
readonly file_data?: string | undefined;
|
|
119
119
|
readonly file_url?: string | undefined;
|
|
120
120
|
} | {
|
|
121
121
|
readonly type: "input_text";
|
|
@@ -190,21 +190,10 @@ export declare const protocol: Protocol<{
|
|
|
190
190
|
readonly [x: string]: unknown;
|
|
191
191
|
readonly type: string;
|
|
192
192
|
readonly status?: unknown;
|
|
193
|
-
readonly code?: unknown;
|
|
194
193
|
readonly message?: unknown;
|
|
194
|
+
readonly code?: unknown;
|
|
195
195
|
readonly text?: string | undefined;
|
|
196
196
|
readonly error?: unknown;
|
|
197
|
-
readonly item?: {
|
|
198
|
-
readonly [x: string]: unknown;
|
|
199
|
-
readonly type: string;
|
|
200
|
-
readonly id?: string | undefined;
|
|
201
|
-
readonly name?: string | undefined;
|
|
202
|
-
readonly namespace?: string | undefined;
|
|
203
|
-
readonly arguments?: string | undefined;
|
|
204
|
-
readonly encrypted_content?: string | null | undefined;
|
|
205
|
-
readonly call_id?: string | undefined;
|
|
206
|
-
} | null | undefined;
|
|
207
|
-
readonly delta?: string | undefined;
|
|
208
197
|
readonly response?: {
|
|
209
198
|
readonly [x: string]: unknown;
|
|
210
199
|
readonly id?: string | undefined;
|
|
@@ -236,6 +225,17 @@ export declare const protocol: Protocol<{
|
|
|
236
225
|
readonly reason?: string | undefined;
|
|
237
226
|
} | null | undefined;
|
|
238
227
|
} | undefined;
|
|
228
|
+
readonly item?: {
|
|
229
|
+
readonly [x: string]: unknown;
|
|
230
|
+
readonly type: string;
|
|
231
|
+
readonly id?: string | undefined;
|
|
232
|
+
readonly name?: string | undefined;
|
|
233
|
+
readonly namespace?: string | undefined;
|
|
234
|
+
readonly arguments?: string | undefined;
|
|
235
|
+
readonly encrypted_content?: string | null | undefined;
|
|
236
|
+
readonly call_id?: string | undefined;
|
|
237
|
+
} | null | undefined;
|
|
238
|
+
readonly delta?: string | undefined;
|
|
239
239
|
readonly arguments?: string | undefined;
|
|
240
240
|
readonly item_id?: string | undefined;
|
|
241
241
|
readonly output_index?: number | undefined;
|
|
@@ -13,6 +13,7 @@ import { JsonObject, knownString, optionalArray, optionalNull, ProviderShared }
|
|
|
13
13
|
import { classifyProviderFailure } from "../provider-error.js";
|
|
14
14
|
import { effortUpdate, resolveEffortUpdates } from "../effort-updates.js";
|
|
15
15
|
import * as Cache from "./utils/cache.js";
|
|
16
|
+
import { claudeVersion, supportsThinkingBlockBinding, THINKING_BINDING_BETA } from "./utils/claude-model.js";
|
|
16
17
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
17
18
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
18
19
|
const ADAPTER = "anthropic-messages";
|
|
@@ -850,20 +851,6 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (re
|
|
|
850
851
|
}
|
|
851
852
|
return messages;
|
|
852
853
|
});
|
|
853
|
-
// Accept gateway namespaces and Vertex suffixes without treating a snapshot date as a minor version.
|
|
854
|
-
const claudeVersion = (id) => {
|
|
855
|
-
const match = /(?:^|[./])claude-(?<family>[a-z]+)-(?<major>\d+)(?:[.-](?<minor>\d{1,2}))?(?:$|[-:@])/.exec(id.toLowerCase())?.groups;
|
|
856
|
-
if (!match)
|
|
857
|
-
return undefined;
|
|
858
|
-
return { family: match.family, major: Number(match.major), minor: Number(match.minor ?? 0) };
|
|
859
|
-
};
|
|
860
|
-
const supportsThinkingBlockBinding = (model) => {
|
|
861
|
-
const override = model.compatibility?.supportsThinkingBlockBinding;
|
|
862
|
-
if (override !== undefined)
|
|
863
|
-
return override;
|
|
864
|
-
const version = claudeVersion(model.id);
|
|
865
|
-
return version !== undefined && (version.major > 5 || (version.major === 5 && version.minor >= 1));
|
|
866
|
-
};
|
|
867
854
|
const supportsEffortUpdates = (model) => {
|
|
868
855
|
const override = model.compatibility?.supportsEffortUpdates;
|
|
869
856
|
if (override !== undefined)
|
|
@@ -1437,7 +1424,7 @@ function requiredBetaHeaders(body) {
|
|
|
1437
1424
|
betas.push("mid-conversation-output-config-2026-07-01");
|
|
1438
1425
|
const thinking = body.thinking;
|
|
1439
1426
|
if (thinking && thinking.type !== "disabled" && thinking.block_binding)
|
|
1440
|
-
betas.push(
|
|
1427
|
+
betas.push(THINKING_BINDING_BETA);
|
|
1441
1428
|
return betas;
|
|
1442
1429
|
}
|
|
1443
1430
|
export const route = Route.make({
|
|
@@ -141,7 +141,7 @@ interface ParserState {
|
|
|
141
141
|
readonly hasToolCalls: boolean;
|
|
142
142
|
readonly lifecycle: Lifecycle.State;
|
|
143
143
|
readonly reasoningSignatures: Readonly<Record<number, string>>;
|
|
144
|
-
readonly reasoningRedactedContent: Readonly<Record<number,
|
|
144
|
+
readonly reasoningRedactedContent: Readonly<Record<number, Uint8Array[]>>;
|
|
145
145
|
}
|
|
146
146
|
/**
|
|
147
147
|
* The Bedrock Converse protocol — request body construction, body schema, and
|
|
@@ -9,6 +9,7 @@ import { JsonObject, optionalArray, ProviderShared } from "./shared.js";
|
|
|
9
9
|
import { BedrockAuth } from "./utils/bedrock-auth.js";
|
|
10
10
|
import { BedrockCache } from "./utils/bedrock-cache.js";
|
|
11
11
|
import { BedrockMedia } from "./utils/bedrock-media.js";
|
|
12
|
+
import { supportsThinkingBlockBinding, THINKING_BINDING_BETA } from "./utils/claude-model.js";
|
|
12
13
|
import { Lifecycle } from "./utils/lifecycle.js";
|
|
13
14
|
import { MistralToolID } from "./utils/mistral-tool-id.js";
|
|
14
15
|
import { ToolStream } from "./utils/tool-stream.js";
|
|
@@ -351,18 +352,33 @@ const Options = Schema.Struct({
|
|
|
351
352
|
const decodeOptions = ProviderShared.validateWith(Schema.decodeUnknownEffect(Options));
|
|
352
353
|
// Claude on Bedrock requires the thinking budget below `maxTokens`, with a minimum of 1,024.
|
|
353
354
|
const MIN_THINKING_BUDGET = 1_024;
|
|
355
|
+
const isThinkingDisabled = Schema.is(Schema.Struct({
|
|
356
|
+
additionalModelRequestFields: Schema.Struct({ thinking: Schema.Struct({ type: Schema.Literal("disabled") }) }),
|
|
357
|
+
}));
|
|
358
|
+
// Claude 5.1+ binds each thinking signature to the prefix above it. Ask Bedrock to drop the affected blocks instead of
|
|
359
|
+
// failing when that prefix changes. `http.body` overlays this field by field, so callers can still override it.
|
|
360
|
+
const applyThinkingBindingDefault = (request, thinking) => {
|
|
361
|
+
if (isThinkingDisabled(request.http?.body))
|
|
362
|
+
return thinking;
|
|
363
|
+
if (!supportsThinkingBlockBinding(request.model))
|
|
364
|
+
return thinking;
|
|
365
|
+
return {
|
|
366
|
+
...(thinking ?? { type: "adaptive" }),
|
|
367
|
+
block_binding: { prefix_mismatch_behavior: "drop_block" },
|
|
368
|
+
};
|
|
369
|
+
};
|
|
354
370
|
const fromRequest = Effect.fn("BedrockConverse.fromRequest")(function* (request) {
|
|
355
371
|
const toolChoice = request.toolChoice ? yield* lowerToolChoice(request.toolChoice) : undefined;
|
|
356
372
|
const flattened = ProviderShared.flattenToolRequest(request);
|
|
357
373
|
const generation = request.generation;
|
|
358
374
|
const options = yield* decodeOptions(request.providerOptions ?? {});
|
|
359
375
|
const maxTokens = isNova2(request.model) && isHighReasoningEffort(request.http?.body) ? undefined : generation?.maxTokens;
|
|
360
|
-
const thinking = options.thinking === undefined
|
|
376
|
+
const thinking = applyThinkingBindingDefault(request, options.thinking === undefined
|
|
361
377
|
? undefined
|
|
362
378
|
: {
|
|
363
379
|
type: "enabled",
|
|
364
380
|
budget_tokens: ProviderShared.fitThinkingBudget(options.thinking.budgetTokens, maxTokens, MIN_THINKING_BUDGET),
|
|
365
|
-
};
|
|
381
|
+
});
|
|
366
382
|
// Bedrock-Claude shares Anthropic's 4-breakpoint cap. Spend the budget in
|
|
367
383
|
// tools → system → messages order to favour the highest-impact prefixes.
|
|
368
384
|
const breakpoints = BedrockCache.breakpoints(request.model.id);
|
|
@@ -407,6 +423,8 @@ const fromRequest = Effect.fn("BedrockConverse.fromRequest")(function* (request)
|
|
|
407
423
|
: {
|
|
408
424
|
...(generation?.topK === undefined ? {} : { top_k: generation.topK }),
|
|
409
425
|
...(thinking === undefined ? {} : { thinking }),
|
|
426
|
+
// Converse takes Anthropic betas in the body, and Bedrock rejects `block_binding` without this one.
|
|
427
|
+
...(thinking?.block_binding === undefined ? {} : { anthropic_beta: [THINKING_BINDING_BETA] }),
|
|
410
428
|
},
|
|
411
429
|
};
|
|
412
430
|
});
|
|
@@ -478,25 +496,29 @@ const step = (state, event) => Effect.gen(function* () {
|
|
|
478
496
|
const index = event.contentBlockDelta.contentBlockIndex;
|
|
479
497
|
const reasoning = event.contentBlockDelta.delta.reasoningContent;
|
|
480
498
|
const events = [];
|
|
481
|
-
const
|
|
499
|
+
const redactedChunk = yield* (() => {
|
|
482
500
|
if (reasoning.redactedContent === undefined)
|
|
483
501
|
return Effect.succeed(undefined);
|
|
484
|
-
return Effect.fromResult(Encoding.decodeBase64(reasoning.redactedContent)).pipe(Effect.
|
|
502
|
+
return Effect.fromResult(Encoding.decodeBase64(reasoning.redactedContent)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "Bedrock Converse reasoningContent.redactedContent contains invalid base64 data", undefined, cause)));
|
|
485
503
|
})();
|
|
486
|
-
const
|
|
504
|
+
const redactedChunks = state.reasoningRedactedContent[index] ?? [];
|
|
505
|
+
if (redactedChunk !== undefined)
|
|
506
|
+
redactedChunks.push(redactedChunk);
|
|
487
507
|
const metadata = (() => {
|
|
488
508
|
if (reasoning.signature)
|
|
489
509
|
return providerMetadata(state.providerMetadataKey, { signature: reasoning.signature });
|
|
490
|
-
if (
|
|
491
|
-
return providerMetadata(state.providerMetadataKey, { redactedData });
|
|
510
|
+
if (redactedChunk === undefined && reasoning.data !== undefined)
|
|
511
|
+
return providerMetadata(state.providerMetadataKey, { redactedData: reasoning.data });
|
|
492
512
|
})();
|
|
493
513
|
const lifecycle = (() => {
|
|
494
|
-
if (reasoning.text
|
|
495
|
-
return state.lifecycle;
|
|
496
|
-
|
|
514
|
+
if (reasoning.text !== undefined || metadata !== undefined)
|
|
515
|
+
return Lifecycle.reasoningDelta(state.lifecycle, events, `reasoning-${index}`, reasoning.text ?? "", metadata);
|
|
516
|
+
if (redactedChunk !== undefined)
|
|
517
|
+
return Lifecycle.reasoningStart(state.lifecycle, events, `reasoning-${index}`);
|
|
518
|
+
return state.lifecycle;
|
|
497
519
|
})();
|
|
498
520
|
const reasoningRedactedContent = (() => {
|
|
499
|
-
if (
|
|
521
|
+
if (redactedChunk !== undefined)
|
|
500
522
|
return { ...state.reasoningRedactedContent, [index]: redactedChunks };
|
|
501
523
|
if (reasoning.data === undefined)
|
|
502
524
|
return state.reasoningRedactedContent;
|
|
@@ -609,7 +631,11 @@ const onHalt = (state) => {
|
|
|
609
631
|
return state.finishReason.normalized;
|
|
610
632
|
})();
|
|
611
633
|
const events = [];
|
|
612
|
-
|
|
634
|
+
const lifecycle = Object.entries(state.reasoningRedactedContent).reduce((current, [index, chunks]) => {
|
|
635
|
+
const signature = state.reasoningSignatures[Number(index)];
|
|
636
|
+
return Lifecycle.reasoningEnd(current, events, `reasoning-${index}`, providerMetadata(state.providerMetadataKey, signature ? { signature } : { redactedData: encodeRedactedContent(chunks) }));
|
|
637
|
+
}, state.lifecycle);
|
|
638
|
+
Lifecycle.finish(lifecycle, events, {
|
|
613
639
|
reason: {
|
|
614
640
|
...state.finishReason,
|
|
615
641
|
normalized,
|