@opencode-ai/ai 0.0.0-bootstrap.0 → 0.0.0-next-15618
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 +176 -1
- package/dist/cache-policy.d.ts +2 -0
- package/dist/cache-policy.js +108 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +9 -0
- package/dist/llm.d.ts +223 -0
- package/dist/llm.js +81 -0
- package/dist/protocols/anthropic-messages.d.ts +503 -0
- package/dist/protocols/anthropic-messages.js +745 -0
- package/dist/protocols/bedrock-converse.d.ts +476 -0
- package/dist/protocols/bedrock-converse.js +526 -0
- package/dist/protocols/bedrock-event-stream.d.ts +9 -0
- package/dist/protocols/bedrock-event-stream.js +64 -0
- package/dist/protocols/gemini.d.ts +220 -0
- package/dist/protocols/gemini.js +420 -0
- package/dist/protocols/google-vertex-anthropic.d.ts +494 -0
- package/dist/protocols/google-vertex-anthropic.js +33 -0
- package/dist/protocols/google-vertex-gemini.d.ts +59 -0
- package/dist/protocols/google-vertex-gemini.js +18 -0
- package/dist/protocols/index.d.ts +9 -0
- package/dist/protocols/index.js +9 -0
- package/dist/protocols/openai-chat.d.ts +391 -0
- package/dist/protocols/openai-chat.js +423 -0
- package/dist/protocols/openai-compatible-chat.d.ts +73 -0
- package/dist/protocols/openai-compatible-chat.js +20 -0
- package/dist/protocols/openai-compatible-responses.d.ts +85 -0
- package/dist/protocols/openai-compatible-responses.js +18 -0
- package/dist/protocols/openai-responses.d.ts +613 -0
- package/dist/protocols/openai-responses.js +828 -0
- package/dist/protocols/shared.d.ts +192 -0
- package/dist/protocols/shared.js +236 -0
- package/dist/protocols/utils/bedrock-auth.d.ts +18 -0
- package/dist/protocols/utils/bedrock-auth.js +41 -0
- package/dist/protocols/utils/bedrock-cache.d.ts +15 -0
- package/dist/protocols/utils/bedrock-cache.js +29 -0
- package/dist/protocols/utils/bedrock-media.d.ts +49 -0
- package/dist/protocols/utils/bedrock-media.js +68 -0
- package/dist/protocols/utils/cache.d.ts +6 -0
- package/dist/protocols/utils/cache.js +8 -0
- package/dist/protocols/utils/gemini-tool-schema.d.ts +2 -0
- package/dist/protocols/utils/gemini-tool-schema.js +83 -0
- package/dist/protocols/utils/lifecycle.d.ts +19 -0
- package/dist/protocols/utils/lifecycle.js +65 -0
- package/dist/protocols/utils/openai-options.d.ts +22 -0
- package/dist/protocols/utils/openai-options.js +64 -0
- package/dist/protocols/utils/tool-schema.d.ts +7 -0
- package/dist/protocols/utils/tool-schema.js +79 -0
- package/dist/protocols/utils/tool-stream.d.ts +155 -0
- package/dist/protocols/utils/tool-stream.js +136 -0
- package/dist/protocols.d.ts +1 -0
- package/dist/protocols.js +1 -0
- package/dist/provider-error.d.ts +13 -0
- package/dist/provider-error.js +122 -0
- package/dist/provider-package.d.ts +13 -0
- package/dist/provider-package.js +1 -0
- package/dist/provider.d.ts +23 -0
- package/dist/provider.js +2 -0
- package/dist/providers/amazon-bedrock.d.ts +150 -0
- package/dist/providers/amazon-bedrock.js +41 -0
- package/dist/providers/anthropic-compatible.d.ts +183 -0
- package/dist/providers/anthropic-compatible.js +44 -0
- package/dist/providers/anthropic.d.ts +181 -0
- package/dist/providers/anthropic.js +39 -0
- package/dist/providers/azure/chat.d.ts +2 -0
- package/dist/providers/azure/chat.js +1 -0
- package/dist/providers/azure/responses.d.ts +2 -0
- package/dist/providers/azure/responses.js +1 -0
- package/dist/providers/azure.d.ts +183 -0
- package/dist/providers/azure.js +89 -0
- package/dist/providers/cloudflare.d.ts +232 -0
- package/dist/providers/cloudflare.js +87 -0
- package/dist/providers/github-copilot.d.ts +167 -0
- package/dist/providers/github-copilot.js +47 -0
- package/dist/providers/google-vertex/anthropic.d.ts +2 -0
- package/dist/providers/google-vertex/anthropic.js +1 -0
- package/dist/providers/google-vertex-anthropic.d.ts +181 -0
- package/dist/providers/google-vertex-anthropic.js +46 -0
- package/dist/providers/google-vertex-shared.d.ts +22 -0
- package/dist/providers/google-vertex-shared.js +61 -0
- package/dist/providers/google-vertex.d.ts +93 -0
- package/dist/providers/google-vertex.js +49 -0
- package/dist/providers/google.d.ts +85 -0
- package/dist/providers/google.js +33 -0
- package/dist/providers/index.d.ts +15 -0
- package/dist/providers/index.js +15 -0
- package/dist/providers/openai/chat.d.ts +2 -0
- package/dist/providers/openai/chat.js +1 -0
- package/dist/providers/openai/responses.d.ts +2 -0
- package/dist/providers/openai/responses.js +1 -0
- package/dist/providers/openai-compatible/responses.d.ts +1 -0
- package/dist/providers/openai-compatible/responses.js +1 -0
- package/dist/providers/openai-compatible-profile.d.ts +43 -0
- package/dist/providers/openai-compatible-profile.js +12 -0
- package/dist/providers/openai-compatible-responses.d.ts +107 -0
- package/dist/providers/openai-compatible-responses.js +33 -0
- package/dist/providers/openai-compatible.d.ts +130 -0
- package/dist/providers/openai-compatible.js +55 -0
- package/dist/providers/openai-options.d.ts +30 -0
- package/dist/providers/openai-options.js +42 -0
- package/dist/providers/openai.d.ts +264 -0
- package/dist/providers/openai.js +62 -0
- package/dist/providers/openrouter.d.ts +321 -0
- package/dist/providers/openrouter.js +65 -0
- package/dist/providers/xai.d.ts +169 -0
- package/dist/providers/xai.js +43 -0
- package/dist/providers.d.ts +1 -0
- package/dist/providers.js +1 -0
- package/dist/route/auth-options.d.ts +34 -0
- package/dist/route/auth-options.js +14 -0
- package/dist/route/auth.d.ts +49 -0
- package/dist/route/auth.js +89 -0
- package/dist/route/client.d.ts +330 -0
- package/dist/route/client.js +211 -0
- package/dist/route/endpoint.d.ts +28 -0
- package/dist/route/endpoint.js +21 -0
- package/dist/route/executor.d.ts +12 -0
- package/dist/route/executor.js +229 -0
- package/dist/route/framing.d.ts +23 -0
- package/dist/route/framing.js +4 -0
- package/dist/route/index.d.ts +16 -0
- package/dist/route/index.js +9 -0
- package/dist/route/protocol.d.ts +76 -0
- package/dist/route/protocol.js +17 -0
- package/dist/route/transport/http.d.ts +33 -0
- package/dist/route/transport/http.js +98 -0
- package/dist/route/transport/index.d.ts +27 -0
- package/dist/route/transport/index.js +4 -0
- package/dist/route/transport/websocket.d.ts +56 -0
- package/dist/route/transport/websocket.js +181 -0
- package/dist/route.d.ts +1 -0
- package/dist/route.js +1 -0
- package/dist/schema/errors.d.ts +153 -0
- package/dist/schema/errors.js +150 -0
- package/dist/schema/events.d.ts +4292 -0
- package/dist/schema/events.js +482 -0
- package/dist/schema/ids.d.ts +30 -0
- package/dist/schema/ids.js +18 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/messages.d.ts +385 -0
- package/dist/schema/messages.js +246 -0
- package/dist/schema/options.d.ts +143 -0
- package/dist/schema/options.js +195 -0
- package/dist/tool-runtime.d.ts +15 -0
- package/dist/tool-runtime.js +35 -0
- package/dist/tool.d.ts +134 -0
- package/dist/tool.js +66 -0
- package/dist/utils/record.d.ts +2 -0
- package/dist/utils/record.js +2 -0
- package/package.json +36 -9
- package/index.js +0 -1
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Shared helpers for provider cache-marker lowering. Anthropic and Bedrock
|
|
2
|
+
// both enforce a 4-breakpoint cap per request and accept the same `5m`/`1h`
|
|
3
|
+
// TTL buckets, so the counter and TTL mapping live here.
|
|
4
|
+
export const newBreakpoints = (cap) => ({ remaining: cap, dropped: 0 });
|
|
5
|
+
// Returns `"1h"` for any `ttlSeconds >= 3600`, otherwise `undefined` (the
|
|
6
|
+
// provider default 5m). Anthropic & Bedrock both treat anything shorter than
|
|
7
|
+
// an hour as 5m.
|
|
8
|
+
export const ttlBucket = (ttlSeconds) => ttlSeconds !== undefined && ttlSeconds >= 3600 ? "1h" : undefined;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { isRecord } from "../../utils/record";
|
|
2
|
+
// Gemini accepts a JSON Schema-like dialect for tool parameters, but rejects a
|
|
3
|
+
// handful of common JSON Schema shapes. Keep this projection isolated so the
|
|
4
|
+
// Gemini protocol file still reads like the other protocol modules.
|
|
5
|
+
const SCHEMA_INTENT_KEYS = [
|
|
6
|
+
"type",
|
|
7
|
+
"properties",
|
|
8
|
+
"items",
|
|
9
|
+
"prefixItems",
|
|
10
|
+
"enum",
|
|
11
|
+
"const",
|
|
12
|
+
"$ref",
|
|
13
|
+
"additionalProperties",
|
|
14
|
+
"patternProperties",
|
|
15
|
+
"required",
|
|
16
|
+
"not",
|
|
17
|
+
"if",
|
|
18
|
+
"then",
|
|
19
|
+
"else",
|
|
20
|
+
];
|
|
21
|
+
const hasCombiner = (schema) => isRecord(schema) && (Array.isArray(schema.anyOf) || Array.isArray(schema.oneOf) || Array.isArray(schema.allOf));
|
|
22
|
+
const hasSchemaIntent = (schema) => isRecord(schema) && (hasCombiner(schema) || SCHEMA_INTENT_KEYS.some((key) => key in schema));
|
|
23
|
+
const sanitizeNode = (schema) => {
|
|
24
|
+
if (!isRecord(schema))
|
|
25
|
+
return Array.isArray(schema) ? schema.map(sanitizeNode) : schema;
|
|
26
|
+
const result = Object.fromEntries(Object.entries(schema).map(([key, value]) => [
|
|
27
|
+
key,
|
|
28
|
+
key === "enum" && Array.isArray(value) ? value.map(String) : sanitizeNode(value),
|
|
29
|
+
]));
|
|
30
|
+
if (Array.isArray(result.enum) && (result.type === "integer" || result.type === "number"))
|
|
31
|
+
result.type = "string";
|
|
32
|
+
const properties = result.properties;
|
|
33
|
+
if (result.type === "object" && isRecord(properties) && Array.isArray(result.required)) {
|
|
34
|
+
result.required = result.required.filter((field) => typeof field === "string" && field in properties);
|
|
35
|
+
}
|
|
36
|
+
if (result.type === "array" && !hasCombiner(result)) {
|
|
37
|
+
result.items = result.items ?? {};
|
|
38
|
+
if (isRecord(result.items) && !hasSchemaIntent(result.items))
|
|
39
|
+
result.items = { ...result.items, type: "string" };
|
|
40
|
+
}
|
|
41
|
+
if (typeof result.type === "string" && result.type !== "object" && !hasCombiner(result)) {
|
|
42
|
+
delete result.properties;
|
|
43
|
+
delete result.required;
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
const emptyObjectSchema = (schema) => schema.type === "object" &&
|
|
48
|
+
(!isRecord(schema.properties) || Object.keys(schema.properties).length === 0) &&
|
|
49
|
+
!schema.additionalProperties;
|
|
50
|
+
const projectNode = (schema) => {
|
|
51
|
+
if (!isRecord(schema))
|
|
52
|
+
return undefined;
|
|
53
|
+
if (emptyObjectSchema(schema))
|
|
54
|
+
return undefined;
|
|
55
|
+
return Object.fromEntries([
|
|
56
|
+
["description", schema.description],
|
|
57
|
+
["required", schema.required],
|
|
58
|
+
["format", schema.format],
|
|
59
|
+
["type", Array.isArray(schema.type) ? schema.type.filter((type) => type !== "null")[0] : schema.type],
|
|
60
|
+
["nullable", Array.isArray(schema.type) && schema.type.includes("null") ? true : undefined],
|
|
61
|
+
["enum", schema.const !== undefined ? [schema.const] : schema.enum],
|
|
62
|
+
[
|
|
63
|
+
"properties",
|
|
64
|
+
isRecord(schema.properties)
|
|
65
|
+
? Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [key, projectNode(value)]))
|
|
66
|
+
: undefined,
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"items",
|
|
70
|
+
Array.isArray(schema.items)
|
|
71
|
+
? schema.items.map(projectNode)
|
|
72
|
+
: schema.items === undefined
|
|
73
|
+
? undefined
|
|
74
|
+
: projectNode(schema.items),
|
|
75
|
+
],
|
|
76
|
+
["allOf", Array.isArray(schema.allOf) ? schema.allOf.map(projectNode) : undefined],
|
|
77
|
+
["anyOf", Array.isArray(schema.anyOf) ? schema.anyOf.map(projectNode) : undefined],
|
|
78
|
+
["oneOf", Array.isArray(schema.oneOf) ? schema.oneOf.map(projectNode) : undefined],
|
|
79
|
+
["minLength", schema.minLength],
|
|
80
|
+
].filter((entry) => entry[1] !== undefined));
|
|
81
|
+
};
|
|
82
|
+
export const convert = (schema) => projectNode(sanitizeNode(schema));
|
|
83
|
+
export * as GeminiToolSchema from "./gemini-tool-schema";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LLMEvent, type FinishReason, type ProviderMetadata, type Usage } from "../../schema";
|
|
2
|
+
export interface State {
|
|
3
|
+
readonly stepStarted: boolean;
|
|
4
|
+
readonly text: ReadonlySet<string>;
|
|
5
|
+
readonly reasoning: ReadonlySet<string>;
|
|
6
|
+
}
|
|
7
|
+
export declare const initial: () => State;
|
|
8
|
+
export declare const stepStart: (state: State, events: LLMEvent[]) => State;
|
|
9
|
+
export declare const textDelta: (state: State, events: LLMEvent[], id: string, text: string) => State;
|
|
10
|
+
export declare const reasoningStart: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
|
|
11
|
+
export declare const reasoningDelta: (state: State, events: LLMEvent[], id: string, text: string, providerMetadata?: ProviderMetadata) => State;
|
|
12
|
+
export declare const reasoningEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
|
|
13
|
+
export declare const textEnd: (state: State, events: LLMEvent[], id: string, providerMetadata?: ProviderMetadata) => State;
|
|
14
|
+
export declare const finish: (state: State, events: LLMEvent[], input: {
|
|
15
|
+
readonly reason: FinishReason;
|
|
16
|
+
readonly usage?: Usage;
|
|
17
|
+
readonly providerMetadata?: ProviderMetadata;
|
|
18
|
+
}) => State;
|
|
19
|
+
export * as Lifecycle from "./lifecycle";
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { LLMEvent } from "../../schema";
|
|
2
|
+
export const initial = () => ({ stepStarted: false, text: new Set(), reasoning: new Set() });
|
|
3
|
+
export const stepStart = (state, events) => {
|
|
4
|
+
if (state.stepStarted)
|
|
5
|
+
return state;
|
|
6
|
+
events.push(LLMEvent.stepStart({ index: 0 }));
|
|
7
|
+
return { ...state, stepStarted: true };
|
|
8
|
+
};
|
|
9
|
+
export const textDelta = (state, events, id, text) => {
|
|
10
|
+
const stepped = stepStart(state, events);
|
|
11
|
+
if (stepped.text.has(id)) {
|
|
12
|
+
events.push(LLMEvent.textDelta({ id, text }));
|
|
13
|
+
return stepped;
|
|
14
|
+
}
|
|
15
|
+
events.push(LLMEvent.textStart({ id }), LLMEvent.textDelta({ id, text }));
|
|
16
|
+
return { ...stepped, text: new Set([...stepped.text, id]) };
|
|
17
|
+
};
|
|
18
|
+
export const reasoningStart = (state, events, id, providerMetadata) => {
|
|
19
|
+
if (state.reasoning.has(id))
|
|
20
|
+
return state;
|
|
21
|
+
const stepped = stepStart(state, events);
|
|
22
|
+
events.push(LLMEvent.reasoningStart({ id, providerMetadata }));
|
|
23
|
+
return { ...stepped, reasoning: new Set([...stepped.reasoning, id]) };
|
|
24
|
+
};
|
|
25
|
+
export const reasoningDelta = (state, events, id, text, providerMetadata) => {
|
|
26
|
+
const started = reasoningStart(state, events, id, providerMetadata);
|
|
27
|
+
events.push(LLMEvent.reasoningDelta({ id, text }));
|
|
28
|
+
return started;
|
|
29
|
+
};
|
|
30
|
+
export const reasoningEnd = (state, events, id, providerMetadata) => {
|
|
31
|
+
if (!state.reasoning.has(id))
|
|
32
|
+
return state;
|
|
33
|
+
const stepped = stepStart(state, events);
|
|
34
|
+
events.push(LLMEvent.reasoningEnd({ id, providerMetadata }));
|
|
35
|
+
const reasoning = new Set(stepped.reasoning);
|
|
36
|
+
reasoning.delete(id);
|
|
37
|
+
return { ...stepped, reasoning };
|
|
38
|
+
};
|
|
39
|
+
export const textEnd = (state, events, id, providerMetadata) => {
|
|
40
|
+
if (!state.text.has(id))
|
|
41
|
+
return state;
|
|
42
|
+
const stepped = stepStart(state, events);
|
|
43
|
+
events.push(LLMEvent.textEnd({ id, providerMetadata }));
|
|
44
|
+
const text = new Set(stepped.text);
|
|
45
|
+
text.delete(id);
|
|
46
|
+
return { ...stepped, text };
|
|
47
|
+
};
|
|
48
|
+
const closeOpenBlocks = (state, events) => {
|
|
49
|
+
for (const id of state.reasoning)
|
|
50
|
+
events.push(LLMEvent.reasoningEnd({ id }));
|
|
51
|
+
for (const id of state.text)
|
|
52
|
+
events.push(LLMEvent.textEnd({ id }));
|
|
53
|
+
return { ...state, text: new Set(), reasoning: new Set() };
|
|
54
|
+
};
|
|
55
|
+
export const finish = (state, events, input) => {
|
|
56
|
+
const stepped = closeOpenBlocks(stepStart(state, events), events);
|
|
57
|
+
events.push(LLMEvent.stepFinish({
|
|
58
|
+
index: 0,
|
|
59
|
+
reason: input.reason,
|
|
60
|
+
usage: input.usage,
|
|
61
|
+
providerMetadata: input.providerMetadata,
|
|
62
|
+
}), LLMEvent.finish(input));
|
|
63
|
+
return { ...stepped, stepStarted: false };
|
|
64
|
+
};
|
|
65
|
+
export * as Lifecycle from "./lifecycle";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import type { LLMRequest } from "../../schema";
|
|
3
|
+
export declare const OpenAIReasoningEfforts: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
4
|
+
export type OpenAIReasoningEffort = string;
|
|
5
|
+
export declare const OpenAIResponseIncludables: readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"];
|
|
6
|
+
export type OpenAIResponseIncludable = (typeof OpenAIResponseIncludables)[number];
|
|
7
|
+
export declare const OpenAIServiceTiers: readonly ["auto", "default", "flex", "priority"];
|
|
8
|
+
export type OpenAIServiceTier = (typeof OpenAIServiceTiers)[number];
|
|
9
|
+
export declare const OpenAIReasoningEffort: Schema.String;
|
|
10
|
+
export declare const OpenAITextVerbosity: Schema.Literals<readonly ["low", "medium", "high"]>;
|
|
11
|
+
export declare const OpenAIResponseIncludable: Schema.Literals<readonly ["file_search_call.results", "web_search_call.results", "web_search_call.action.sources", "message.input_image.image_url", "computer_call_output.output.image_url", "code_interpreter_call.outputs", "reasoning.encrypted_content", "message.output_text.logprobs"]>;
|
|
12
|
+
export declare const OpenAIServiceTier: Schema.Literals<readonly ["auto", "default", "flex", "priority"]>;
|
|
13
|
+
export declare const isReasoningEffort: (effort: unknown) => effort is OpenAIReasoningEffort;
|
|
14
|
+
export declare const store: (request: LLMRequest) => boolean | undefined;
|
|
15
|
+
export declare const reasoningEffort: (request: LLMRequest) => string | undefined;
|
|
16
|
+
export declare const reasoningSummary: (request: LLMRequest) => "auto" | undefined;
|
|
17
|
+
export declare const include: (request: LLMRequest) => ReadonlyArray<OpenAIResponseIncludable> | undefined;
|
|
18
|
+
export declare const promptCacheKey: (request: LLMRequest) => string | undefined;
|
|
19
|
+
export declare const textVerbosity: (request: LLMRequest) => "low" | "medium" | "high" | undefined;
|
|
20
|
+
export declare const serviceTier: (request: LLMRequest) => "auto" | "default" | "flex" | "priority" | undefined;
|
|
21
|
+
export declare const instructions: (request: LLMRequest) => string | undefined;
|
|
22
|
+
export * as OpenAIOptions from "./openai-options";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { ReasoningEfforts, TextVerbosity } from "../../schema";
|
|
3
|
+
export const OpenAIReasoningEfforts = ReasoningEfforts;
|
|
4
|
+
// Mirrors OpenAI's `ResponseIncludable` union from the official SDK. Keep this
|
|
5
|
+
// in lockstep with `openai-node/src/resources/responses/responses.ts`.
|
|
6
|
+
export const OpenAIResponseIncludables = [
|
|
7
|
+
"file_search_call.results",
|
|
8
|
+
"web_search_call.results",
|
|
9
|
+
"web_search_call.action.sources",
|
|
10
|
+
"message.input_image.image_url",
|
|
11
|
+
"computer_call_output.output.image_url",
|
|
12
|
+
"code_interpreter_call.outputs",
|
|
13
|
+
"reasoning.encrypted_content",
|
|
14
|
+
"message.output_text.logprobs",
|
|
15
|
+
];
|
|
16
|
+
export const OpenAIServiceTiers = ["auto", "default", "flex", "priority"];
|
|
17
|
+
const TEXT_VERBOSITY = new Set(["low", "medium", "high"]);
|
|
18
|
+
const INCLUDABLES = new Set(OpenAIResponseIncludables);
|
|
19
|
+
const SERVICE_TIERS = new Set(OpenAIServiceTiers);
|
|
20
|
+
export const OpenAIReasoningEffort = Schema.String;
|
|
21
|
+
export const OpenAITextVerbosity = TextVerbosity;
|
|
22
|
+
export const OpenAIResponseIncludable = Schema.Literals(OpenAIResponseIncludables);
|
|
23
|
+
export const OpenAIServiceTier = Schema.Literals(OpenAIServiceTiers);
|
|
24
|
+
export const isReasoningEffort = (effort) => typeof effort === "string";
|
|
25
|
+
const isTextVerbosity = (value) => typeof value === "string" && TEXT_VERBOSITY.has(value);
|
|
26
|
+
const options = (request) => request.providerOptions?.openai;
|
|
27
|
+
export const store = (request) => {
|
|
28
|
+
const value = options(request)?.store;
|
|
29
|
+
return typeof value === "boolean" ? value : undefined;
|
|
30
|
+
};
|
|
31
|
+
export const reasoningEffort = (request) => {
|
|
32
|
+
const value = options(request)?.reasoningEffort;
|
|
33
|
+
return typeof value === "string" ? value : undefined;
|
|
34
|
+
};
|
|
35
|
+
export const reasoningSummary = (request) => options(request)?.reasoningSummary === "auto" ? "auto" : undefined;
|
|
36
|
+
// Resolve the OpenAI Responses `include` field. Filters out unknown
|
|
37
|
+
// includable values defensively so a typo in upstream config drops the
|
|
38
|
+
// invalid entry instead of poisoning the wire body. An empty array (either
|
|
39
|
+
// passed directly or produced by filtering) is treated as "no include" and
|
|
40
|
+
// returns undefined so the request body omits the field entirely.
|
|
41
|
+
export const include = (request) => {
|
|
42
|
+
const value = options(request)?.include;
|
|
43
|
+
if (!Array.isArray(value))
|
|
44
|
+
return undefined;
|
|
45
|
+
const filtered = value.filter((entry) => INCLUDABLES.has(entry));
|
|
46
|
+
return filtered.length > 0 ? filtered : undefined;
|
|
47
|
+
};
|
|
48
|
+
export const promptCacheKey = (request) => {
|
|
49
|
+
const value = options(request)?.promptCacheKey;
|
|
50
|
+
return typeof value === "string" ? value : undefined;
|
|
51
|
+
};
|
|
52
|
+
export const textVerbosity = (request) => {
|
|
53
|
+
const value = options(request)?.textVerbosity;
|
|
54
|
+
return isTextVerbosity(value) ? value : undefined;
|
|
55
|
+
};
|
|
56
|
+
export const serviceTier = (request) => {
|
|
57
|
+
const value = options(request)?.serviceTier;
|
|
58
|
+
return typeof value === "string" && SERVICE_TIERS.has(value) ? value : undefined;
|
|
59
|
+
};
|
|
60
|
+
export const instructions = (request) => {
|
|
61
|
+
const value = options(request)?.instructions;
|
|
62
|
+
return typeof value === "string" ? value : undefined;
|
|
63
|
+
};
|
|
64
|
+
export * as OpenAIOptions from "./openai-options";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JsonSchema, ModelToolSchemaCompatibility } from "../../schema";
|
|
2
|
+
export declare const ToolSchemaProjection: {
|
|
3
|
+
readonly gemini: (schema: JsonSchema) => JsonSchema;
|
|
4
|
+
readonly modelCompatibility: (schema: JsonSchema, compatibility: ModelToolSchemaCompatibility | undefined) => JsonSchema;
|
|
5
|
+
readonly moonshot: (schema: JsonSchema) => JsonSchema;
|
|
6
|
+
readonly openAI: (schema: JsonSchema) => JsonSchema;
|
|
7
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isRecord } from "../../utils/record";
|
|
2
|
+
import { GeminiToolSchema } from "./gemini-tool-schema";
|
|
3
|
+
const removeNullSchemas = (value) => {
|
|
4
|
+
if (Array.isArray(value))
|
|
5
|
+
return value.map(removeNullSchemas);
|
|
6
|
+
if (!isRecord(value))
|
|
7
|
+
return value;
|
|
8
|
+
const fields = Object.fromEntries(Object.entries(value)
|
|
9
|
+
.filter(([key]) => key !== "anyOf")
|
|
10
|
+
.map(([key, field]) => [key, removeNullSchemas(field)]));
|
|
11
|
+
if (!Array.isArray(value.anyOf))
|
|
12
|
+
return fields;
|
|
13
|
+
const variants = value.anyOf.filter((variant) => !isRecord(variant) || variant.type !== "null").map(removeNullSchemas);
|
|
14
|
+
if (variants.length === 1 && isRecord(variants[0]))
|
|
15
|
+
return { ...fields, ...variants[0] };
|
|
16
|
+
return { ...fields, anyOf: variants };
|
|
17
|
+
};
|
|
18
|
+
const tupleItemsSchema = (items) => {
|
|
19
|
+
const projected = items.map(moonshotNode);
|
|
20
|
+
if (projected.length === 0)
|
|
21
|
+
return {};
|
|
22
|
+
if (projected.length === 1)
|
|
23
|
+
return projected[0];
|
|
24
|
+
return { anyOf: projected };
|
|
25
|
+
};
|
|
26
|
+
const moonshotNode = (schema) => {
|
|
27
|
+
if (Array.isArray(schema))
|
|
28
|
+
return schema.map(moonshotNode);
|
|
29
|
+
if (!isRecord(schema))
|
|
30
|
+
return schema;
|
|
31
|
+
if (typeof schema.$ref === "string")
|
|
32
|
+
return { $ref: schema.$ref };
|
|
33
|
+
return Object.fromEntries(Object.entries(schema).flatMap(([key, value]) => {
|
|
34
|
+
if (key === "items" && Array.isArray(value))
|
|
35
|
+
return [[key, tupleItemsSchema(value)]];
|
|
36
|
+
if (key === "prefixItems") {
|
|
37
|
+
if ("items" in schema)
|
|
38
|
+
return [];
|
|
39
|
+
return [["items", tupleItemsSchema(Array.isArray(value) ? value : [])]];
|
|
40
|
+
}
|
|
41
|
+
if (key === "unevaluatedItems")
|
|
42
|
+
return [];
|
|
43
|
+
return [[key, moonshotNode(value)]];
|
|
44
|
+
}));
|
|
45
|
+
};
|
|
46
|
+
const moonshot = (schema) => {
|
|
47
|
+
const projected = moonshotNode(schema);
|
|
48
|
+
return isRecord(projected) ? projected : {};
|
|
49
|
+
};
|
|
50
|
+
const openAI = (schema) => {
|
|
51
|
+
const variants = Array.isArray(schema.anyOf) ? schema.anyOf.filter(isRecord) : [];
|
|
52
|
+
const flattened = variants.length === 0
|
|
53
|
+
? { ...schema, type: "object" }
|
|
54
|
+
: {
|
|
55
|
+
...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "anyOf")),
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: variants.reduce((properties, variant) => ({ ...(isRecord(variant.properties) ? variant.properties : {}), ...properties }), {}),
|
|
58
|
+
additionalProperties: false,
|
|
59
|
+
};
|
|
60
|
+
const normalized = removeNullSchemas(flattened);
|
|
61
|
+
return isRecord(normalized) ? normalized : { type: "object" };
|
|
62
|
+
};
|
|
63
|
+
const gemini = (schema) => GeminiToolSchema.convert(schema) ?? {};
|
|
64
|
+
const modelCompatibility = (schema, compatibility) => {
|
|
65
|
+
if (compatibility === undefined)
|
|
66
|
+
return schema;
|
|
67
|
+
switch (compatibility) {
|
|
68
|
+
case "gemini":
|
|
69
|
+
return gemini(schema);
|
|
70
|
+
case "moonshot":
|
|
71
|
+
return moonshot(schema);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
export const ToolSchemaProjection = {
|
|
75
|
+
gemini,
|
|
76
|
+
modelCompatibility,
|
|
77
|
+
moonshot,
|
|
78
|
+
openAI,
|
|
79
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { LLMError, LLMEvent, type ProviderMetadata } from "../../schema";
|
|
3
|
+
import { type ToolAccumulator } from "../shared";
|
|
4
|
+
type StreamKey = string | number;
|
|
5
|
+
/**
|
|
6
|
+
* One pending streamed tool call. Providers emit the tool identity and JSON
|
|
7
|
+
* argument text across separate chunks; `input` is the raw JSON string collected
|
|
8
|
+
* so far, not the parsed object.
|
|
9
|
+
*/
|
|
10
|
+
export interface PendingTool extends ToolAccumulator {
|
|
11
|
+
readonly providerExecuted?: boolean;
|
|
12
|
+
readonly providerMetadata?: ProviderMetadata;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Sparse parser state keyed by the provider's stream-local tool identifier.
|
|
16
|
+
*
|
|
17
|
+
* This key is not the final tool-call id (`call_...`). It is the id/index the
|
|
18
|
+
* provider uses while streaming a partial call: OpenAI Chat / Anthropic /
|
|
19
|
+
* Bedrock use numeric content indexes, while OpenAI Responses uses string
|
|
20
|
+
* `item_id`s. The generic keeps each protocol internally consistent.
|
|
21
|
+
*/
|
|
22
|
+
export type State<K extends StreamKey> = Partial<Record<K, PendingTool>>;
|
|
23
|
+
/**
|
|
24
|
+
* Result of adding argument text to one pending tool call. It returns both the
|
|
25
|
+
* next `tools` state and the updated `tool` because parsers often need the
|
|
26
|
+
* current id/name immediately. `events` contains lifecycle and delta events
|
|
27
|
+
* produced by the append; metadata-only deltas update identity without output.
|
|
28
|
+
*/
|
|
29
|
+
export interface AppendOutcome<K extends StreamKey> {
|
|
30
|
+
readonly tools: State<K>;
|
|
31
|
+
readonly tool: PendingTool;
|
|
32
|
+
readonly events: ReadonlyArray<LLMEvent>;
|
|
33
|
+
}
|
|
34
|
+
/** Create empty accumulator state for one provider stream. */
|
|
35
|
+
export declare const empty: <K extends StreamKey>() => State<K>;
|
|
36
|
+
export declare const isError: <K extends StreamKey>(result: AppendOutcome<K> | LLMError) => result is LLMError;
|
|
37
|
+
/**
|
|
38
|
+
* Register a tool call whose start event arrived before any argument deltas.
|
|
39
|
+
* Used by Anthropic `content_block_start`, Bedrock `contentBlockStart`, and
|
|
40
|
+
* OpenAI Responses `response.output_item.added`.
|
|
41
|
+
*/
|
|
42
|
+
export declare const start: <K extends StreamKey>(tools: State<K>, key: K, tool: Omit<PendingTool, "input"> & {
|
|
43
|
+
readonly input?: string;
|
|
44
|
+
}) => Partial<Record<K, PendingTool>>;
|
|
45
|
+
/**
|
|
46
|
+
* Append a streamed argument delta, starting the tool if this provider encodes
|
|
47
|
+
* identity on the first delta instead of a separate start event. OpenAI Chat has
|
|
48
|
+
* this shape: `tool_calls[].index` is the stream key, and `id` / `name` may only
|
|
49
|
+
* appear on the first delta for that index.
|
|
50
|
+
*/
|
|
51
|
+
export declare const appendOrStart: <K extends StreamKey>(route: string, tools: State<K>, key: K, delta: {
|
|
52
|
+
readonly id?: string;
|
|
53
|
+
readonly name?: string;
|
|
54
|
+
readonly text: string;
|
|
55
|
+
}, missingToolMessage: string) => AppendOutcome<K> | LLMError;
|
|
56
|
+
/**
|
|
57
|
+
* Append argument text to a tool that must already have been started. This keeps
|
|
58
|
+
* protocols honest when their stream grammar promises a start event before any
|
|
59
|
+
* argument delta.
|
|
60
|
+
*/
|
|
61
|
+
export declare const appendExisting: <K extends StreamKey>(route: string, tools: State<K>, key: K, text: string, missingToolMessage: string) => AppendOutcome<K> | LLMError;
|
|
62
|
+
/**
|
|
63
|
+
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
|
|
64
|
+
* from state, and return the optional public `tool-call` event. Missing keys are
|
|
65
|
+
* a no-op because some providers emit stop events for non-tool content blocks.
|
|
66
|
+
*/
|
|
67
|
+
export declare const finish: <K extends StreamKey>(route: string, tools: State<K>, key: K) => Effect.Effect<{
|
|
68
|
+
tools: Partial<Record<K, PendingTool>>;
|
|
69
|
+
events?: undefined;
|
|
70
|
+
} | {
|
|
71
|
+
tools: Partial<Record<K, PendingTool>>;
|
|
72
|
+
events: ({
|
|
73
|
+
readonly type: "tool-input-end";
|
|
74
|
+
readonly id: string;
|
|
75
|
+
readonly name: string;
|
|
76
|
+
readonly providerMetadata?: {
|
|
77
|
+
readonly [x: string]: {
|
|
78
|
+
readonly [x: string]: unknown;
|
|
79
|
+
};
|
|
80
|
+
} | undefined;
|
|
81
|
+
} | {
|
|
82
|
+
readonly type: "tool-call";
|
|
83
|
+
readonly id: string;
|
|
84
|
+
readonly name: string;
|
|
85
|
+
readonly input: unknown;
|
|
86
|
+
readonly providerExecuted?: boolean | undefined;
|
|
87
|
+
readonly providerMetadata?: {
|
|
88
|
+
readonly [x: string]: {
|
|
89
|
+
readonly [x: string]: unknown;
|
|
90
|
+
};
|
|
91
|
+
} | undefined;
|
|
92
|
+
})[];
|
|
93
|
+
}, LLMError, never>;
|
|
94
|
+
/**
|
|
95
|
+
* Finalize one pending tool call with an authoritative final input string.
|
|
96
|
+
* OpenAI Responses can send accumulated deltas and then repeat the completed
|
|
97
|
+
* arguments on `response.output_item.done`; the final value wins.
|
|
98
|
+
*/
|
|
99
|
+
export declare const finishWithInput: <K extends StreamKey>(route: string, tools: State<K>, key: K, input: string) => Effect.Effect<{
|
|
100
|
+
tools: Partial<Record<K, PendingTool>>;
|
|
101
|
+
events?: undefined;
|
|
102
|
+
} | {
|
|
103
|
+
tools: Partial<Record<K, PendingTool>>;
|
|
104
|
+
events: ({
|
|
105
|
+
readonly type: "tool-input-end";
|
|
106
|
+
readonly id: string;
|
|
107
|
+
readonly name: string;
|
|
108
|
+
readonly providerMetadata?: {
|
|
109
|
+
readonly [x: string]: {
|
|
110
|
+
readonly [x: string]: unknown;
|
|
111
|
+
};
|
|
112
|
+
} | undefined;
|
|
113
|
+
} | {
|
|
114
|
+
readonly type: "tool-call";
|
|
115
|
+
readonly id: string;
|
|
116
|
+
readonly name: string;
|
|
117
|
+
readonly input: unknown;
|
|
118
|
+
readonly providerExecuted?: boolean | undefined;
|
|
119
|
+
readonly providerMetadata?: {
|
|
120
|
+
readonly [x: string]: {
|
|
121
|
+
readonly [x: string]: unknown;
|
|
122
|
+
};
|
|
123
|
+
} | undefined;
|
|
124
|
+
})[];
|
|
125
|
+
}, LLMError, never>;
|
|
126
|
+
/**
|
|
127
|
+
* Finalize every pending tool call at once. OpenAI Chat has this shape: it does
|
|
128
|
+
* not emit per-tool stop events, so all accumulated calls finish when the choice
|
|
129
|
+
* receives a terminal `finish_reason`.
|
|
130
|
+
*/
|
|
131
|
+
export declare const finishAll: <K extends StreamKey>(route: string, tools: State<K>) => Effect.Effect<{
|
|
132
|
+
tools: Partial<Record<K, PendingTool>>;
|
|
133
|
+
events: ({
|
|
134
|
+
readonly type: "tool-input-end";
|
|
135
|
+
readonly id: string;
|
|
136
|
+
readonly name: string;
|
|
137
|
+
readonly providerMetadata?: {
|
|
138
|
+
readonly [x: string]: {
|
|
139
|
+
readonly [x: string]: unknown;
|
|
140
|
+
};
|
|
141
|
+
} | undefined;
|
|
142
|
+
} | {
|
|
143
|
+
readonly type: "tool-call";
|
|
144
|
+
readonly id: string;
|
|
145
|
+
readonly name: string;
|
|
146
|
+
readonly input: unknown;
|
|
147
|
+
readonly providerExecuted?: boolean | undefined;
|
|
148
|
+
readonly providerMetadata?: {
|
|
149
|
+
readonly [x: string]: {
|
|
150
|
+
readonly [x: string]: unknown;
|
|
151
|
+
};
|
|
152
|
+
} | undefined;
|
|
153
|
+
})[];
|
|
154
|
+
}, LLMError, never>;
|
|
155
|
+
export * as ToolStream from "./tool-stream";
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { LLMError, LLMEvent } from "../../schema";
|
|
3
|
+
import { eventError, parseToolInput } from "../shared";
|
|
4
|
+
/** Create empty accumulator state for one provider stream. */
|
|
5
|
+
export const empty = () => ({});
|
|
6
|
+
const withTool = (tools, key, tool) => {
|
|
7
|
+
return { ...tools, [key]: tool };
|
|
8
|
+
};
|
|
9
|
+
const withoutTool = (tools, key) => {
|
|
10
|
+
const next = { ...tools };
|
|
11
|
+
delete next[key];
|
|
12
|
+
return next;
|
|
13
|
+
};
|
|
14
|
+
const inputStart = (tool) => LLMEvent.toolInputStart({
|
|
15
|
+
id: tool.id,
|
|
16
|
+
name: tool.name,
|
|
17
|
+
providerMetadata: tool.providerMetadata,
|
|
18
|
+
});
|
|
19
|
+
const inputDelta = (tool, text) => LLMEvent.toolInputDelta({
|
|
20
|
+
id: tool.id,
|
|
21
|
+
name: tool.name,
|
|
22
|
+
text,
|
|
23
|
+
});
|
|
24
|
+
const toolCall = (route, tool, inputOverride) => parseToolInput(route, tool.name, inputOverride ?? tool.input).pipe(Effect.map((input) => LLMEvent.toolCall({
|
|
25
|
+
id: tool.id,
|
|
26
|
+
name: tool.name,
|
|
27
|
+
input,
|
|
28
|
+
providerExecuted: tool.providerExecuted ? true : undefined,
|
|
29
|
+
providerMetadata: tool.providerMetadata,
|
|
30
|
+
})));
|
|
31
|
+
/** Store the updated tool and produce the optional public delta event. */
|
|
32
|
+
const appendTool = (tools, key, tool, text) => {
|
|
33
|
+
const events = [];
|
|
34
|
+
if (!tools[key])
|
|
35
|
+
events.push(inputStart(tool));
|
|
36
|
+
if (text.length > 0)
|
|
37
|
+
events.push(inputDelta(tool, text));
|
|
38
|
+
return {
|
|
39
|
+
tools: withTool(tools, key, tool),
|
|
40
|
+
tool,
|
|
41
|
+
events,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
export const isError = (result) => result instanceof LLMError;
|
|
45
|
+
/**
|
|
46
|
+
* Register a tool call whose start event arrived before any argument deltas.
|
|
47
|
+
* Used by Anthropic `content_block_start`, Bedrock `contentBlockStart`, and
|
|
48
|
+
* OpenAI Responses `response.output_item.added`.
|
|
49
|
+
*/
|
|
50
|
+
export const start = (tools, key, tool) => withTool(tools, key, { ...tool, input: tool.input ?? "" });
|
|
51
|
+
/**
|
|
52
|
+
* Append a streamed argument delta, starting the tool if this provider encodes
|
|
53
|
+
* identity on the first delta instead of a separate start event. OpenAI Chat has
|
|
54
|
+
* this shape: `tool_calls[].index` is the stream key, and `id` / `name` may only
|
|
55
|
+
* appear on the first delta for that index.
|
|
56
|
+
*/
|
|
57
|
+
export const appendOrStart = (route, tools, key, delta, missingToolMessage) => {
|
|
58
|
+
const current = tools[key];
|
|
59
|
+
const id = delta.id ?? current?.id;
|
|
60
|
+
const name = delta.name ?? current?.name;
|
|
61
|
+
if (!id || !name)
|
|
62
|
+
return eventError(route, missingToolMessage);
|
|
63
|
+
const tool = {
|
|
64
|
+
id,
|
|
65
|
+
name,
|
|
66
|
+
input: `${current?.input ?? ""}${delta.text}`,
|
|
67
|
+
providerExecuted: current?.providerExecuted,
|
|
68
|
+
providerMetadata: current?.providerMetadata,
|
|
69
|
+
};
|
|
70
|
+
if (current && delta.text.length === 0 && current.id === id && current.name === name)
|
|
71
|
+
return { tools, tool: current, events: [] };
|
|
72
|
+
return appendTool(tools, key, tool, delta.text);
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Append argument text to a tool that must already have been started. This keeps
|
|
76
|
+
* protocols honest when their stream grammar promises a start event before any
|
|
77
|
+
* argument delta.
|
|
78
|
+
*/
|
|
79
|
+
export const appendExisting = (route, tools, key, text, missingToolMessage) => {
|
|
80
|
+
const current = tools[key];
|
|
81
|
+
if (!current)
|
|
82
|
+
return eventError(route, missingToolMessage);
|
|
83
|
+
if (text.length === 0)
|
|
84
|
+
return { tools, tool: current, events: [] };
|
|
85
|
+
return appendTool(tools, key, { ...current, input: `${current.input}${text}` }, text);
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Finalize one pending tool call: parse the accumulated raw JSON, remove it
|
|
89
|
+
* from state, and return the optional public `tool-call` event. Missing keys are
|
|
90
|
+
* a no-op because some providers emit stop events for non-tool content blocks.
|
|
91
|
+
*/
|
|
92
|
+
export const finish = (route, tools, key) => Effect.gen(function* () {
|
|
93
|
+
const tool = tools[key];
|
|
94
|
+
if (!tool)
|
|
95
|
+
return { tools };
|
|
96
|
+
return {
|
|
97
|
+
tools: withoutTool(tools, key),
|
|
98
|
+
events: [
|
|
99
|
+
LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
|
|
100
|
+
yield* toolCall(route, tool),
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Finalize one pending tool call with an authoritative final input string.
|
|
106
|
+
* OpenAI Responses can send accumulated deltas and then repeat the completed
|
|
107
|
+
* arguments on `response.output_item.done`; the final value wins.
|
|
108
|
+
*/
|
|
109
|
+
export const finishWithInput = (route, tools, key, input) => Effect.gen(function* () {
|
|
110
|
+
const tool = tools[key];
|
|
111
|
+
if (!tool)
|
|
112
|
+
return { tools };
|
|
113
|
+
return {
|
|
114
|
+
tools: withoutTool(tools, key),
|
|
115
|
+
events: [
|
|
116
|
+
LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
|
|
117
|
+
yield* toolCall(route, tool, input),
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
/**
|
|
122
|
+
* Finalize every pending tool call at once. OpenAI Chat has this shape: it does
|
|
123
|
+
* not emit per-tool stop events, so all accumulated calls finish when the choice
|
|
124
|
+
* receives a terminal `finish_reason`.
|
|
125
|
+
*/
|
|
126
|
+
export const finishAll = (route, tools) => Effect.gen(function* () {
|
|
127
|
+
const pending = Object.values(tools).filter((tool) => tool !== undefined);
|
|
128
|
+
return {
|
|
129
|
+
tools: empty(),
|
|
130
|
+
events: yield* Effect.forEach(pending, (tool) => toolCall(route, tool).pipe(Effect.map((call) => [
|
|
131
|
+
LLMEvent.toolInputEnd({ id: tool.id, name: tool.name, providerMetadata: tool.providerMetadata }),
|
|
132
|
+
call,
|
|
133
|
+
]))).pipe(Effect.map((events) => events.flat())),
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
export * as ToolStream from "./tool-stream";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./protocols/index";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./protocols/index";
|