@opencode/ai 0.0.0-reserved → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +879 -2
- package/dist/cache-policy.d.ts +2 -0
- package/dist/cache-policy.js +129 -0
- package/dist/image-client.d.ts +19 -0
- package/dist/image-client.js +28 -0
- package/dist/image.d.ts +110 -0
- package/dist/image.js +103 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +12 -0
- package/dist/llm.d.ts +252 -0
- package/dist/llm.js +74 -0
- package/dist/protocols/alibaba-chat.d.ts +225 -0
- package/dist/protocols/alibaba-chat.js +75 -0
- package/dist/protocols/alibaba-messages.d.ts +350 -0
- package/dist/protocols/alibaba-messages.js +40 -0
- package/dist/protocols/alibaba-responses.d.ts +255 -0
- package/dist/protocols/alibaba-responses.js +80 -0
- package/dist/protocols/anthropic-messages.d.ts +980 -0
- package/dist/protocols/anthropic-messages.js +1460 -0
- package/dist/protocols/bedrock-converse.d.ts +499 -0
- package/dist/protocols/bedrock-converse.js +643 -0
- package/dist/protocols/bedrock-event-stream.d.ts +9 -0
- package/dist/protocols/bedrock-event-stream.js +97 -0
- package/dist/protocols/gemini.d.ts +285 -0
- package/dist/protocols/gemini.js +671 -0
- package/dist/protocols/google-images.d.ts +30 -0
- package/dist/protocols/google-images.js +181 -0
- package/dist/protocols/index.d.ts +12 -0
- package/dist/protocols/index.js +12 -0
- package/dist/protocols/meta-images.d.ts +26 -0
- package/dist/protocols/meta-images.js +89 -0
- package/dist/protocols/meta-messages.d.ts +356 -0
- package/dist/protocols/meta-messages.js +41 -0
- package/dist/protocols/meta-responses.d.ts +473 -0
- package/dist/protocols/meta-responses.js +173 -0
- package/dist/protocols/mistral-chat.d.ts +540 -0
- package/dist/protocols/mistral-chat.js +624 -0
- package/dist/protocols/open-responses-channel.d.ts +25 -0
- package/dist/protocols/open-responses-channel.js +141 -0
- package/dist/protocols/open-responses-continuation.d.ts +16 -0
- package/dist/protocols/open-responses-continuation.js +170 -0
- package/dist/protocols/open-responses.d.ts +1715 -0
- package/dist/protocols/open-responses.js +1228 -0
- package/dist/protocols/openai-chat.d.ts +860 -0
- package/dist/protocols/openai-chat.js +966 -0
- package/dist/protocols/openai-compatible-chat.d.ts +109 -0
- package/dist/protocols/openai-compatible-chat.js +19 -0
- package/dist/protocols/openai-compatible-responses.d.ts +172 -0
- package/dist/protocols/openai-compatible-responses.js +18 -0
- package/dist/protocols/openai-images.d.ts +32 -0
- package/dist/protocols/openai-images.js +183 -0
- package/dist/protocols/openai-responses.d.ts +1389 -0
- package/dist/protocols/openai-responses.js +260 -0
- package/dist/protocols/shared.d.ts +193 -0
- package/dist/protocols/shared.js +290 -0
- package/dist/protocols/utils/bedrock-auth.d.ts +60 -0
- package/dist/protocols/utils/bedrock-auth.js +85 -0
- package/dist/protocols/utils/bedrock-cache.d.ts +19 -0
- package/dist/protocols/utils/bedrock-cache.js +49 -0
- package/dist/protocols/utils/bedrock-media.d.ts +55 -0
- package/dist/protocols/utils/bedrock-media.js +73 -0
- package/dist/protocols/utils/cache.d.ts +6 -0
- package/dist/protocols/utils/cache.js +5 -0
- package/dist/protocols/utils/gemini-tool-schema.d.ts +2 -0
- package/dist/protocols/utils/gemini-tool-schema.js +103 -0
- package/dist/protocols/utils/image-input.d.ts +21 -0
- package/dist/protocols/utils/image-input.js +20 -0
- package/dist/protocols/utils/lifecycle.d.ts +24 -0
- package/dist/protocols/utils/lifecycle.js +72 -0
- package/dist/protocols/utils/meta-image.d.ts +2 -0
- package/dist/protocols/utils/meta-image.js +13 -0
- package/dist/protocols/utils/open-responses-options.d.ts +56 -0
- package/dist/protocols/utils/open-responses-options.js +61 -0
- package/dist/protocols/utils/openai-image.d.ts +5 -0
- package/dist/protocols/utils/openai-image.js +18 -0
- package/dist/protocols/utils/openai-options.d.ts +16 -0
- package/dist/protocols/utils/openai-options.js +14 -0
- package/dist/protocols/utils/partial-json-options.d.ts +62 -0
- package/dist/protocols/utils/partial-json-options.js +52 -0
- package/dist/protocols/utils/partial-json.d.ts +8 -0
- package/dist/protocols/utils/partial-json.js +279 -0
- package/dist/protocols/utils/responses-checkpoint.d.ts +4 -0
- package/dist/protocols/utils/responses-checkpoint.js +107 -0
- package/dist/protocols/utils/responses-compaction.d.ts +4 -0
- package/dist/protocols/utils/responses-compaction.js +129 -0
- package/dist/protocols/utils/responses-hosted-tools.d.ts +26 -0
- package/dist/protocols/utils/responses-hosted-tools.js +32 -0
- package/dist/protocols/utils/tool-schema.d.ts +8 -0
- package/dist/protocols/utils/tool-schema.js +54 -0
- package/dist/protocols/utils/tool-stream.d.ts +710 -0
- package/dist/protocols/utils/tool-stream.js +155 -0
- package/dist/protocols/xai-images.d.ts +26 -0
- package/dist/protocols/xai-images.js +106 -0
- package/dist/protocols/xai-responses.d.ts +250 -0
- package/dist/protocols/xai-responses.js +81 -0
- package/dist/protocols/zai-chat.d.ts +202 -0
- package/dist/protocols/zai-chat.js +49 -0
- package/dist/protocols/zai-images.d.ts +22 -0
- package/dist/protocols/zai-images.js +79 -0
- package/dist/protocols/zai-messages.d.ts +344 -0
- package/dist/protocols/zai-messages.js +27 -0
- package/dist/protocols.d.ts +1 -0
- package/dist/protocols.js +1 -0
- package/dist/provider-error.d.ts +15 -0
- package/dist/provider-error.js +125 -0
- package/dist/provider-package.d.ts +15 -0
- package/dist/provider-package.js +1 -0
- package/dist/provider.d.ts +23 -0
- package/dist/provider.js +2 -0
- package/dist/providers/alibaba/chat.d.ts +1 -0
- package/dist/providers/alibaba/chat.js +1 -0
- package/dist/providers/alibaba/messages.d.ts +3 -0
- package/dist/providers/alibaba/messages.js +1 -0
- package/dist/providers/alibaba/responses.d.ts +3 -0
- package/dist/providers/alibaba/responses.js +1 -0
- package/dist/providers/alibaba.d.ts +711 -0
- package/dist/providers/alibaba.js +94 -0
- package/dist/providers/amazon-bedrock/mantle/chat.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle/chat.js +1 -0
- package/dist/providers/amazon-bedrock/mantle/responses.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle/responses.js +1 -0
- package/dist/providers/amazon-bedrock/mantle.d.ts +2 -0
- package/dist/providers/amazon-bedrock/mantle.js +1 -0
- package/dist/providers/amazon-bedrock-mantle.d.ts +313 -0
- package/dist/providers/amazon-bedrock-mantle.js +79 -0
- package/dist/providers/amazon-bedrock.d.ts +173 -0
- package/dist/providers/amazon-bedrock.js +41 -0
- package/dist/providers/anthropic-compatible.d.ts +309 -0
- package/dist/providers/anthropic-compatible.js +50 -0
- package/dist/providers/anthropic.d.ts +307 -0
- package/dist/providers/anthropic.js +42 -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 +598 -0
- package/dist/providers/azure.js +115 -0
- package/dist/providers/baseten.d.ts +228 -0
- package/dist/providers/baseten.js +38 -0
- package/dist/providers/cerebras.d.ts +227 -0
- package/dist/providers/cerebras.js +40 -0
- package/dist/providers/cloudflare-ai-gateway.d.ts +237 -0
- package/dist/providers/cloudflare-ai-gateway.js +65 -0
- package/dist/providers/cloudflare-workers-ai.d.ts +231 -0
- package/dist/providers/cloudflare-workers-ai.js +51 -0
- package/dist/providers/deepinfra.d.ts +227 -0
- package/dist/providers/deepinfra.js +43 -0
- package/dist/providers/deepseek.d.ts +228 -0
- package/dist/providers/deepseek.js +41 -0
- package/dist/providers/fireworks.d.ts +228 -0
- package/dist/providers/fireworks.js +38 -0
- package/dist/providers/google-vertex/chat.d.ts +2 -0
- package/dist/providers/google-vertex/chat.js +1 -0
- package/dist/providers/google-vertex/gemini.d.ts +2 -0
- package/dist/providers/google-vertex/gemini.js +1 -0
- package/dist/providers/google-vertex/messages.d.ts +2 -0
- package/dist/providers/google-vertex/messages.js +1 -0
- package/dist/providers/google-vertex/responses.d.ts +2 -0
- package/dist/providers/google-vertex/responses.js +1 -0
- package/dist/providers/google-vertex-chat.d.ts +132 -0
- package/dist/providers/google-vertex-chat.js +55 -0
- package/dist/providers/google-vertex-messages.d.ts +306 -0
- package/dist/providers/google-vertex-messages.js +75 -0
- package/dist/providers/google-vertex-responses.d.ts +197 -0
- package/dist/providers/google-vertex-responses.js +56 -0
- package/dist/providers/google-vertex-shared.d.ts +22 -0
- package/dist/providers/google-vertex-shared.js +72 -0
- package/dist/providers/google-vertex.d.ts +119 -0
- package/dist/providers/google-vertex.js +98 -0
- package/dist/providers/google.d.ts +114 -0
- package/dist/providers/google.js +42 -0
- package/dist/providers/groq.d.ts +314 -0
- package/dist/providers/groq.js +81 -0
- package/dist/providers/index.d.ts +31 -0
- package/dist/providers/index.js +31 -0
- package/dist/providers/meta/chat.d.ts +2 -0
- package/dist/providers/meta/chat.js +1 -0
- package/dist/providers/meta/messages.d.ts +2 -0
- package/dist/providers/meta/messages.js +1 -0
- package/dist/providers/meta/responses.d.ts +2 -0
- package/dist/providers/meta/responses.js +1 -0
- package/dist/providers/meta.d.ts +636 -0
- package/dist/providers/meta.js +113 -0
- package/dist/providers/minimax/chat.d.ts +4 -0
- package/dist/providers/minimax/chat.js +8 -0
- package/dist/providers/minimax/messages.d.ts +1 -0
- package/dist/providers/minimax/messages.js +1 -0
- package/dist/providers/minimax/responses.d.ts +4 -0
- package/dist/providers/minimax/responses.js +8 -0
- package/dist/providers/minimax.d.ts +598 -0
- package/dist/providers/minimax.js +89 -0
- package/dist/providers/mistral.d.ts +207 -0
- package/dist/providers/mistral.js +28 -0
- package/dist/providers/moonshot/chat.d.ts +1 -0
- package/dist/providers/moonshot/chat.js +1 -0
- package/dist/providers/moonshot/messages.d.ts +4 -0
- package/dist/providers/moonshot/messages.js +8 -0
- package/dist/providers/moonshot/responses.d.ts +4 -0
- package/dist/providers/moonshot/responses.js +8 -0
- package/dist/providers/moonshot.d.ts +596 -0
- package/dist/providers/moonshot.js +90 -0
- package/dist/providers/open-responses-options.d.ts +6 -0
- package/dist/providers/open-responses-options.js +1 -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-responses.d.ts +195 -0
- package/dist/providers/openai-compatible-responses.js +32 -0
- package/dist/providers/openai-compatible.d.ts +130 -0
- package/dist/providers/openai-compatible.js +33 -0
- package/dist/providers/openai-options.d.ts +26 -0
- package/dist/providers/openai-options.js +41 -0
- package/dist/providers/openai.d.ts +404 -0
- package/dist/providers/openai.js +82 -0
- package/dist/providers/openrouter.d.ts +563 -0
- package/dist/providers/openrouter.js +114 -0
- package/dist/providers/togetherai.d.ts +227 -0
- package/dist/providers/togetherai.js +40 -0
- package/dist/providers/xai.d.ts +163 -0
- package/dist/providers/xai.js +88 -0
- package/dist/providers/zai/chat.d.ts +1 -0
- package/dist/providers/zai/chat.js +1 -0
- package/dist/providers/zai-coding-plan/chat.d.ts +1 -0
- package/dist/providers/zai-coding-plan/chat.js +1 -0
- package/dist/providers/zai-coding-plan/messages.d.ts +4 -0
- package/dist/providers/zai-coding-plan/messages.js +8 -0
- package/dist/providers/zai-coding-plan/responses.d.ts +4 -0
- package/dist/providers/zai-coding-plan/responses.js +8 -0
- package/dist/providers/zai-coding-plan.d.ts +577 -0
- package/dist/providers/zai-coding-plan.js +63 -0
- package/dist/providers/zai.d.ts +149 -0
- package/dist/providers/zai.js +53 -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 +51 -0
- package/dist/route/auth.js +87 -0
- package/dist/route/client.d.ts +430 -0
- package/dist/route/client.js +307 -0
- package/dist/route/endpoint.d.ts +28 -0
- package/dist/route/endpoint.js +21 -0
- package/dist/route/executor.d.ts +27 -0
- package/dist/route/executor.js +183 -0
- package/dist/route/framing.d.ts +29 -0
- package/dist/route/framing.js +14 -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 +16 -0
- package/dist/route/transport/http.d.ts +34 -0
- package/dist/route/transport/http.js +68 -0
- package/dist/route/transport/index.d.ts +41 -0
- package/dist/route/transport/index.js +4 -0
- package/dist/route/transport/websocket-channel.d.ts +57 -0
- package/dist/route/transport/websocket-channel.js +1 -0
- package/dist/route/transport/websocket.d.ts +56 -0
- package/dist/route/transport/websocket.js +370 -0
- package/dist/route.d.ts +1 -0
- package/dist/route.js +1 -0
- package/dist/schema/errors.d.ts +170 -0
- package/dist/schema/errors.js +131 -0
- package/dist/schema/events.d.ts +8123 -0
- package/dist/schema/events.js +550 -0
- package/dist/schema/ids.d.ts +17 -0
- package/dist/schema/ids.js +10 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/messages.d.ts +646 -0
- package/dist/schema/messages.js +301 -0
- package/dist/schema/options.d.ts +159 -0
- package/dist/schema/options.js +189 -0
- package/dist/testing.d.ts +1693 -0
- package/dist/testing.js +129 -0
- package/dist/tool-history.d.ts +2 -0
- package/dist/tool-history.js +84 -0
- package/dist/tool-runtime.d.ts +15 -0
- package/dist/tool-runtime.js +62 -0
- package/dist/tool.d.ts +136 -0
- package/dist/tool.js +66 -0
- package/dist/utils/record.d.ts +2 -0
- package/dist/utils/record.js +2 -0
- package/dist/utils/sanitize.d.ts +1 -0
- package/dist/utils/sanitize.js +12 -0
- package/package.json +43 -10
|
@@ -0,0 +1,1228 @@
|
|
|
1
|
+
import { Effect, Option, Schema, SchemaGetter } from "effect";
|
|
2
|
+
import { HttpTransport } from "../route/transport/index.js";
|
|
3
|
+
import { Protocol } from "../route/protocol.js";
|
|
4
|
+
import { AIError, LLMEvent, ProviderInternalError, Usage, } from "../schema/index.js";
|
|
5
|
+
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js";
|
|
6
|
+
import { classifyProviderFailure } from "../provider-error.js";
|
|
7
|
+
import { OpenResponsesOptions } from "./utils/open-responses-options.js";
|
|
8
|
+
import { Lifecycle } from "./utils/lifecycle.js";
|
|
9
|
+
import { ToolSchemaProjection } from "./utils/tool-schema.js";
|
|
10
|
+
import { ToolStream } from "./utils/tool-stream.js";
|
|
11
|
+
const ADAPTER = "open-responses";
|
|
12
|
+
const NAME = "Open Responses";
|
|
13
|
+
export const PATH = "/responses";
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Request Body Schema
|
|
16
|
+
// =============================================================================
|
|
17
|
+
export const OpenResponsesInputText = Schema.Struct({
|
|
18
|
+
type: Schema.tag("input_text"),
|
|
19
|
+
text: Schema.String,
|
|
20
|
+
});
|
|
21
|
+
export const OpenResponsesInputImage = Schema.Struct({
|
|
22
|
+
type: Schema.tag("input_image"),
|
|
23
|
+
image_url: Schema.String,
|
|
24
|
+
detail: Schema.optional(Schema.String),
|
|
25
|
+
});
|
|
26
|
+
export const OpenResponsesInputFile = Schema.Struct({
|
|
27
|
+
type: Schema.tag("input_file"),
|
|
28
|
+
filename: Schema.String,
|
|
29
|
+
detail: Schema.optional(Schema.String),
|
|
30
|
+
file_data: Schema.optional(Schema.String),
|
|
31
|
+
file_url: Schema.optional(Schema.String),
|
|
32
|
+
});
|
|
33
|
+
const OpenResponsesInputVideo = Schema.Struct({
|
|
34
|
+
type: Schema.tag("input_video"),
|
|
35
|
+
video_url: Schema.String,
|
|
36
|
+
});
|
|
37
|
+
const MediaInput = Schema.Union([OpenResponsesInputImage, OpenResponsesInputFile]);
|
|
38
|
+
const OpenResponsesInputContent = Schema.Union([OpenResponsesInputText, MediaInput]);
|
|
39
|
+
export const OpenResponsesOutputText = Schema.Struct({
|
|
40
|
+
type: Schema.tag("output_text"),
|
|
41
|
+
text: Schema.String,
|
|
42
|
+
});
|
|
43
|
+
export const MessagePhase = Schema.NullOr(Schema.Literals(["commentary", "final_answer"]));
|
|
44
|
+
export const MessageMetadata = Schema.Struct({
|
|
45
|
+
itemId: Schema.optional(Schema.String),
|
|
46
|
+
type: Schema.optional(Schema.Literal("message")),
|
|
47
|
+
status: Schema.optional(Schema.String),
|
|
48
|
+
phase: Schema.optional(MessagePhase),
|
|
49
|
+
});
|
|
50
|
+
const messagePhase = (value) => {
|
|
51
|
+
if (value === null || value === "commentary" || value === "final_answer")
|
|
52
|
+
return value;
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
55
|
+
const OpenResponsesReasoningSummaryText = Schema.Struct({
|
|
56
|
+
type: Schema.tag("summary_text"),
|
|
57
|
+
text: Schema.String,
|
|
58
|
+
});
|
|
59
|
+
export const OpenResponsesReasoningItem = Schema.Struct({
|
|
60
|
+
type: Schema.tag("reasoning"),
|
|
61
|
+
id: Schema.optionalKey(Schema.String),
|
|
62
|
+
summary: Schema.Array(OpenResponsesReasoningSummaryText),
|
|
63
|
+
encrypted_content: optionalNull(Schema.String),
|
|
64
|
+
});
|
|
65
|
+
const OpenResponsesWebSearchCall = Schema.StructWithRest(Schema.Struct({
|
|
66
|
+
type: Schema.tag("web_search_call"),
|
|
67
|
+
id: Schema.String,
|
|
68
|
+
status: Schema.optional(Schema.String),
|
|
69
|
+
action: optionalNull(JsonObject),
|
|
70
|
+
}), [JsonObject]);
|
|
71
|
+
const OpenResponsesFileSearchCall = Schema.StructWithRest(Schema.Struct({
|
|
72
|
+
type: Schema.tag("file_search_call"),
|
|
73
|
+
id: Schema.String,
|
|
74
|
+
status: Schema.optional(Schema.String),
|
|
75
|
+
queries: Schema.optional(Schema.Array(Schema.String)),
|
|
76
|
+
results: optionalNull(Schema.Array(JsonObject)),
|
|
77
|
+
}), [JsonObject]);
|
|
78
|
+
const OpenResponsesCodeInterpreterCall = Schema.StructWithRest(Schema.Struct({
|
|
79
|
+
type: Schema.tag("code_interpreter_call"),
|
|
80
|
+
id: Schema.String,
|
|
81
|
+
status: Schema.optional(Schema.String),
|
|
82
|
+
code: optionalNull(Schema.String),
|
|
83
|
+
container_id: optionalNull(Schema.String),
|
|
84
|
+
outputs: optionalNull(Schema.Array(JsonObject)),
|
|
85
|
+
}), [JsonObject]);
|
|
86
|
+
const OpenResponsesMCPCall = Schema.StructWithRest(Schema.Struct({
|
|
87
|
+
type: Schema.tag("mcp_call"),
|
|
88
|
+
id: Schema.String,
|
|
89
|
+
status: Schema.optional(Schema.String),
|
|
90
|
+
server_label: Schema.optional(Schema.String),
|
|
91
|
+
name: Schema.optional(Schema.String),
|
|
92
|
+
arguments: Schema.optional(Schema.String),
|
|
93
|
+
output: optionalNull(Schema.String),
|
|
94
|
+
error: Schema.optional(Schema.Unknown),
|
|
95
|
+
}), [JsonObject]);
|
|
96
|
+
export const HostedToolItem = Schema.Union([
|
|
97
|
+
OpenResponsesWebSearchCall,
|
|
98
|
+
OpenResponsesFileSearchCall,
|
|
99
|
+
OpenResponsesCodeInterpreterCall,
|
|
100
|
+
OpenResponsesMCPCall,
|
|
101
|
+
]);
|
|
102
|
+
// `function_call_output.output` accepts either a plain string or an ordered
|
|
103
|
+
// array of content items so tools can return images and files in addition to text.
|
|
104
|
+
// https://www.openresponses.org/reference
|
|
105
|
+
const OpenResponsesFunctionCallOutputContent = Schema.Union([
|
|
106
|
+
OpenResponsesInputText,
|
|
107
|
+
OpenResponsesInputImage,
|
|
108
|
+
OpenResponsesInputFile,
|
|
109
|
+
OpenResponsesInputVideo,
|
|
110
|
+
]);
|
|
111
|
+
const OpenResponsesFunctionCallOutput = Schema.Union([
|
|
112
|
+
Schema.String,
|
|
113
|
+
Schema.Array(OpenResponsesFunctionCallOutputContent),
|
|
114
|
+
]);
|
|
115
|
+
export const CompactionItem = Schema.Struct({
|
|
116
|
+
type: Schema.Literal("compaction"),
|
|
117
|
+
id: optionalNull(Schema.String),
|
|
118
|
+
encrypted_content: Schema.String,
|
|
119
|
+
});
|
|
120
|
+
export const InputItem = Schema.Union([
|
|
121
|
+
CompactionItem,
|
|
122
|
+
Schema.Struct({ role: Schema.tag("system"), content: Schema.String }),
|
|
123
|
+
Schema.Struct({ role: Schema.tag("developer"), content: Schema.String }),
|
|
124
|
+
Schema.Struct({
|
|
125
|
+
role: Schema.tag("user"),
|
|
126
|
+
content: Schema.Array(OpenResponsesInputContent),
|
|
127
|
+
type: Schema.optional(Schema.Literal("message")),
|
|
128
|
+
id: Schema.optional(Schema.String),
|
|
129
|
+
status: Schema.optional(Schema.String),
|
|
130
|
+
}),
|
|
131
|
+
Schema.Struct({
|
|
132
|
+
type: Schema.tag("message"),
|
|
133
|
+
id: Schema.optionalKey(Schema.String),
|
|
134
|
+
role: Schema.tag("assistant"),
|
|
135
|
+
content: Schema.Array(OpenResponsesOutputText),
|
|
136
|
+
phase: Schema.optionalKey(MessagePhase),
|
|
137
|
+
status: Schema.optional(Schema.String),
|
|
138
|
+
}),
|
|
139
|
+
OpenResponsesReasoningItem,
|
|
140
|
+
Schema.Struct({
|
|
141
|
+
type: Schema.tag("function_call"),
|
|
142
|
+
id: Schema.optionalKey(Schema.String),
|
|
143
|
+
call_id: Schema.String,
|
|
144
|
+
name: Schema.String,
|
|
145
|
+
namespace: Schema.optional(Schema.String),
|
|
146
|
+
arguments: Schema.String,
|
|
147
|
+
}),
|
|
148
|
+
Schema.Struct({
|
|
149
|
+
type: Schema.tag("function_call_output"),
|
|
150
|
+
call_id: Schema.String,
|
|
151
|
+
output: OpenResponsesFunctionCallOutput,
|
|
152
|
+
}),
|
|
153
|
+
HostedToolItem,
|
|
154
|
+
]);
|
|
155
|
+
export const Tool = Schema.Struct({
|
|
156
|
+
type: Schema.tag("function"),
|
|
157
|
+
name: Schema.String,
|
|
158
|
+
description: Schema.String,
|
|
159
|
+
parameters: JsonObject,
|
|
160
|
+
strict: Schema.optional(Schema.Boolean),
|
|
161
|
+
});
|
|
162
|
+
export const ToolChoice = Schema.Union([
|
|
163
|
+
Schema.Literals(["auto", "none", "required"]),
|
|
164
|
+
Schema.Struct({ type: Schema.tag("function"), name: Schema.String }),
|
|
165
|
+
Schema.Struct({
|
|
166
|
+
type: Schema.tag("allowed_tools"),
|
|
167
|
+
mode: Schema.Literals(["auto", "none", "required"]),
|
|
168
|
+
tools: Schema.Array(Schema.Struct({ type: Schema.tag("function"), name: Schema.String })),
|
|
169
|
+
}),
|
|
170
|
+
]);
|
|
171
|
+
// Fields shared between the HTTP body and the WebSocket `response.create`
|
|
172
|
+
// message. The HTTP body adds `stream: true`; the WebSocket message adds
|
|
173
|
+
// `type: "response.create"`. Defining the shared shape once keeps the two
|
|
174
|
+
// transports in sync without a destructure-and-strip dance.
|
|
175
|
+
export const coreFields = {
|
|
176
|
+
model: Schema.String,
|
|
177
|
+
input: Schema.Array(InputItem),
|
|
178
|
+
instructions: Schema.optional(Schema.String),
|
|
179
|
+
tools: optionalArray(Tool),
|
|
180
|
+
tool_choice: Schema.optional(ToolChoice),
|
|
181
|
+
store: Schema.optional(Schema.Boolean),
|
|
182
|
+
metadata: Schema.optional(Schema.Record(Schema.String, Schema.String)),
|
|
183
|
+
safety_identifier: Schema.optional(Schema.String),
|
|
184
|
+
stream_options: Schema.optional(Schema.Struct({
|
|
185
|
+
include_obfuscation: Schema.optional(Schema.Boolean),
|
|
186
|
+
})),
|
|
187
|
+
top_logprobs: Schema.optional(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 20 }))),
|
|
188
|
+
truncation: Schema.optional(OpenResponsesOptions.TruncationSchema),
|
|
189
|
+
service_tier: Schema.optional(OpenResponsesOptions.ServiceTierSchema),
|
|
190
|
+
prompt_cache_key: Schema.optional(Schema.String),
|
|
191
|
+
include: optionalArray(OpenResponsesOptions.ResponseIncludableSchema),
|
|
192
|
+
reasoning: Schema.optional(Schema.Struct({
|
|
193
|
+
effort: Schema.optional(OpenResponsesOptions.ReasoningEffort),
|
|
194
|
+
summary: Schema.optional(Schema.Literals(["auto", "concise", "detailed"])),
|
|
195
|
+
})),
|
|
196
|
+
text: Schema.optional(Schema.Struct({
|
|
197
|
+
verbosity: Schema.optional(OpenResponsesOptions.TextVerbositySchema),
|
|
198
|
+
})),
|
|
199
|
+
max_output_tokens: Schema.optional(Schema.Number),
|
|
200
|
+
max_tool_calls: Schema.optional(Schema.Int),
|
|
201
|
+
parallel_tool_calls: Schema.optional(Schema.Boolean),
|
|
202
|
+
temperature: Schema.optional(Schema.Number),
|
|
203
|
+
top_p: Schema.optional(Schema.Number),
|
|
204
|
+
presence_penalty: Schema.optional(Schema.Number),
|
|
205
|
+
frequency_penalty: Schema.optional(Schema.Number),
|
|
206
|
+
};
|
|
207
|
+
const OpenResponsesBody = Schema.Struct({
|
|
208
|
+
...coreFields,
|
|
209
|
+
stream: Schema.Literal(true),
|
|
210
|
+
});
|
|
211
|
+
export const OpenResponsesUsage = Schema.Struct({
|
|
212
|
+
input_tokens: Schema.optional(Schema.Number),
|
|
213
|
+
input_tokens_details: optionalNull(Schema.Struct({
|
|
214
|
+
cached_tokens: Schema.optional(Schema.Number),
|
|
215
|
+
cache_write_tokens: Schema.optional(Schema.Number),
|
|
216
|
+
})),
|
|
217
|
+
output_tokens: Schema.optional(Schema.Number),
|
|
218
|
+
output_tokens_details: optionalNull(Schema.Struct({ reasoning_tokens: Schema.optional(Schema.Number) })),
|
|
219
|
+
total_tokens: Schema.optional(Schema.Number),
|
|
220
|
+
});
|
|
221
|
+
// The spec requires `id` on every output item, but some gateways drop it from
|
|
222
|
+
// later item events (Bedrock Mantle renames it to `item_id` on
|
|
223
|
+
// `output_item.done` and `response.completed.output`). Decode it as optional
|
|
224
|
+
// and let `normalize` recover or mint it once before the parser runs.
|
|
225
|
+
// https://www.openresponses.org/specification#extending-items
|
|
226
|
+
export const StreamItem = Schema.StructWithRest(Schema.Struct({
|
|
227
|
+
type: Schema.String,
|
|
228
|
+
id: Schema.optional(Schema.String),
|
|
229
|
+
call_id: Schema.optional(Schema.String),
|
|
230
|
+
name: Schema.optional(Schema.String),
|
|
231
|
+
namespace: Schema.optional(Schema.String),
|
|
232
|
+
arguments: Schema.optional(Schema.String),
|
|
233
|
+
encrypted_content: optionalNull(Schema.String),
|
|
234
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
235
|
+
// Responses-compatible providers put streaming error details at the top level or
|
|
236
|
+
// under `error`, and response failures under `response.error`. Accept all three shapes.
|
|
237
|
+
// https://www.openresponses.org/specification
|
|
238
|
+
const OpenResponsesErrorPayload = Schema.Struct({
|
|
239
|
+
type: optionalNull(Schema.String),
|
|
240
|
+
code: optionalNull(Schema.String),
|
|
241
|
+
message: optionalNull(Schema.String),
|
|
242
|
+
param: optionalNull(Schema.String),
|
|
243
|
+
});
|
|
244
|
+
const WebSocketErrorHeader = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]);
|
|
245
|
+
export const WebSocketErrorEvent = Schema.StructWithRest(Schema.Struct({
|
|
246
|
+
type: Schema.tag("error"),
|
|
247
|
+
status: Schema.optional(Schema.Number),
|
|
248
|
+
status_code: Schema.optional(Schema.Number),
|
|
249
|
+
code: optionalNull(Schema.String),
|
|
250
|
+
message: Schema.optional(Schema.String),
|
|
251
|
+
param: optionalNull(Schema.String),
|
|
252
|
+
error: optionalNull(OpenResponsesErrorPayload),
|
|
253
|
+
headers: Schema.optional(Schema.Record(Schema.String, WebSocketErrorHeader)),
|
|
254
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)]);
|
|
255
|
+
const decodeWebSocketErrorEvent = Schema.decodeUnknownEffect(WebSocketErrorEvent);
|
|
256
|
+
export const decodeKnownErrorEvent = (event) => decodeWebSocketErrorEvent({
|
|
257
|
+
...event,
|
|
258
|
+
status: typeof event.status === "number" ? event.status : undefined,
|
|
259
|
+
status_code: typeof event.status_code === "number" ? event.status_code : undefined,
|
|
260
|
+
headers: ProviderShared.isRecord(event.headers)
|
|
261
|
+
? Object.fromEntries(Object.entries(event.headers).filter((entry) => typeof entry[1] === "string" || typeof entry[1] === "number" || typeof entry[1] === "boolean"))
|
|
262
|
+
: undefined,
|
|
263
|
+
});
|
|
264
|
+
export const Event = Schema.StructWithRest(Schema.Struct({
|
|
265
|
+
type: Schema.String,
|
|
266
|
+
delta: Schema.optional(Schema.String),
|
|
267
|
+
arguments: Schema.optional(Schema.String),
|
|
268
|
+
text: Schema.optional(Schema.String),
|
|
269
|
+
item_id: Schema.optional(Schema.String),
|
|
270
|
+
output_index: Schema.optional(Schema.Number),
|
|
271
|
+
summary_index: Schema.optional(Schema.Number),
|
|
272
|
+
// OutputItemAdded/Done permit a null item in the Open Responses OpenAPI schema.
|
|
273
|
+
item: optionalNull(StreamItem),
|
|
274
|
+
response: Schema.optional(Schema.StructWithRest(Schema.Struct({
|
|
275
|
+
id: Schema.optional(Schema.String),
|
|
276
|
+
service_tier: optionalNull(Schema.String),
|
|
277
|
+
incomplete_details: optionalNull(Schema.Struct({ reason: Schema.optional(Schema.String) })),
|
|
278
|
+
output: Schema.optional(Schema.Array(StreamItem)),
|
|
279
|
+
usage: optionalNull(OpenResponsesUsage),
|
|
280
|
+
error: optionalNull(OpenResponsesErrorPayload),
|
|
281
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)])),
|
|
282
|
+
code: optionalNull(Schema.String),
|
|
283
|
+
message: Schema.optional(Schema.String),
|
|
284
|
+
param: optionalNull(Schema.String),
|
|
285
|
+
error: optionalNull(OpenResponsesErrorPayload),
|
|
286
|
+
status: Schema.optional(Schema.Unknown),
|
|
287
|
+
status_code: Schema.optional(Schema.Unknown),
|
|
288
|
+
headers: Schema.optional(Schema.Unknown),
|
|
289
|
+
}), [Schema.Record(Schema.String, Schema.Unknown)]).pipe(Schema.decode({
|
|
290
|
+
decode: SchemaGetter.transform((event) => {
|
|
291
|
+
if (event.type !== "error" || event.error != null)
|
|
292
|
+
return event;
|
|
293
|
+
const { code, message, param, ...rest } = event;
|
|
294
|
+
if (code === undefined && message === undefined && param === undefined)
|
|
295
|
+
return event;
|
|
296
|
+
// Flat errors (for example, Meta's) can also arrive through generic Responses endpoints.
|
|
297
|
+
return { ...rest, error: { code, message, param } };
|
|
298
|
+
}),
|
|
299
|
+
encode: SchemaGetter.passthrough(),
|
|
300
|
+
}));
|
|
301
|
+
const decodeEventValue = Schema.decodeUnknownEffect(Event);
|
|
302
|
+
const decodeFrame = Schema.decodeUnknownEffect(ProviderShared.Json);
|
|
303
|
+
/**
|
|
304
|
+
* Decodes one WebSocket frame. xAI answers a rejected `response.create` with `{ "error": { "message", "type" } }` and no
|
|
305
|
+
* event type; that envelope reads as an error event so the failure classifies instead of failing decoding.
|
|
306
|
+
*/
|
|
307
|
+
export const decodeChannelEvent = (frame) => decodeFrame(frame).pipe(Effect.flatMap((value) => decodeEventValue(ProviderShared.isRecord(value) && value.type === undefined && ProviderShared.isRecord(value.error)
|
|
308
|
+
? { ...value, type: "error" }
|
|
309
|
+
: value)));
|
|
310
|
+
const BASE_ADAPTER = { id: ADAPTER, name: NAME };
|
|
311
|
+
// =============================================================================
|
|
312
|
+
// Request Lowering
|
|
313
|
+
// =============================================================================
|
|
314
|
+
export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool, inputSchema) {
|
|
315
|
+
if (tool.native !== undefined)
|
|
316
|
+
return yield* ProviderShared.invalidRequest(`${protocolName} does not support provider-native tool ${tool.name}`);
|
|
317
|
+
return {
|
|
318
|
+
type: "function",
|
|
319
|
+
name: tool.name,
|
|
320
|
+
description: tool.description,
|
|
321
|
+
parameters: inputSchema,
|
|
322
|
+
// The common tool definition does not currently express Responses strict-schema policy.
|
|
323
|
+
strict: false,
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
export const lowerToolChoice = (protocolName, toolChoice) => ProviderShared.matchToolChoice(protocolName, toolChoice, {
|
|
327
|
+
auto: () => "auto",
|
|
328
|
+
none: () => "none",
|
|
329
|
+
required: () => "required",
|
|
330
|
+
tool: (toolName) => ({ type: "function", name: toolName }),
|
|
331
|
+
});
|
|
332
|
+
// Server-issued item ids need a nonempty prefix and suffix, but the prefix is
|
|
333
|
+
// provider-defined and does not necessarily identify the item's semantic type.
|
|
334
|
+
const itemID = (providerMetadata, providerMetadataKey) => {
|
|
335
|
+
const metadata = providerMetadata?.[providerMetadataKey];
|
|
336
|
+
if (!ProviderShared.isRecord(metadata) || typeof metadata.itemId !== "string")
|
|
337
|
+
return undefined;
|
|
338
|
+
const separator = metadata.itemId.indexOf("_");
|
|
339
|
+
return separator > 0 && separator < metadata.itemId.length - 1 ? metadata.itemId : undefined;
|
|
340
|
+
};
|
|
341
|
+
const lowerToolCall = (part, providerMetadataKey) => {
|
|
342
|
+
const id = itemID(part.providerMetadata, providerMetadataKey);
|
|
343
|
+
return {
|
|
344
|
+
type: "function_call",
|
|
345
|
+
...(id === undefined ? {} : { id }),
|
|
346
|
+
call_id: part.id,
|
|
347
|
+
name: part.name,
|
|
348
|
+
namespace: part.namespace,
|
|
349
|
+
arguments: ProviderShared.encodeJson(part.input),
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
const lowerReasoning = (part, providerMetadataKey) => {
|
|
353
|
+
const metadata = part.providerMetadata?.[providerMetadataKey];
|
|
354
|
+
if (!ProviderShared.isRecord(metadata))
|
|
355
|
+
return undefined;
|
|
356
|
+
const id = itemID(part.providerMetadata, providerMetadataKey);
|
|
357
|
+
const encryptedContent = typeof metadata.reasoningEncryptedContent === "string" || metadata.reasoningEncryptedContent === null
|
|
358
|
+
? metadata.reasoningEncryptedContent
|
|
359
|
+
: undefined;
|
|
360
|
+
return {
|
|
361
|
+
type: "reasoning",
|
|
362
|
+
...(id === undefined ? {} : { id }),
|
|
363
|
+
summary: part.text.length > 0 ? [{ type: "summary_text", text: part.text }] : [],
|
|
364
|
+
encrypted_content: encryptedContent,
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (part, request, adapter, target) {
|
|
368
|
+
const media = ProviderShared.normalizeMedia(part);
|
|
369
|
+
const providerMedia = adapter.lowerMedia?.({ part, media, request });
|
|
370
|
+
if (providerMedia)
|
|
371
|
+
return providerMedia;
|
|
372
|
+
const detail = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenResponsesInputImage.fields.detail))(part.providerMetadata?.[metadataKey(request.model)]?.detail);
|
|
373
|
+
const url = typeof part.data === "string" && (part.data.startsWith("https://") || part.data.startsWith("http://"))
|
|
374
|
+
? part.data
|
|
375
|
+
: undefined;
|
|
376
|
+
if (!media.mime.startsWith("image/")) {
|
|
377
|
+
if (target === "tool-result" && media.mime.startsWith("video/"))
|
|
378
|
+
return { type: "input_video", video_url: url ?? media.dataUrl };
|
|
379
|
+
return {
|
|
380
|
+
type: "input_file",
|
|
381
|
+
filename: part.filename ?? (media.mime === "application/pdf" ? "document.pdf" : "file"),
|
|
382
|
+
detail,
|
|
383
|
+
...(url ? { file_url: url } : { file_data: media.dataUrl }),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return {
|
|
387
|
+
type: "input_image",
|
|
388
|
+
image_url: url ?? media.dataUrl,
|
|
389
|
+
detail,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
const lowerUserContent = Effect.fnUntraced(function* (part, request, adapter) {
|
|
393
|
+
if (part.type === "text")
|
|
394
|
+
return { type: "input_text", text: part.text };
|
|
395
|
+
if (part.type === "media")
|
|
396
|
+
return yield* lowerMessageMedia(part, request, adapter);
|
|
397
|
+
return yield* ProviderShared.unsupportedContent(adapter.name, "user", ["text", "media"]);
|
|
398
|
+
});
|
|
399
|
+
const lowerMessageMedia = Effect.fnUntraced(function* (part, request, adapter) {
|
|
400
|
+
const lowered = yield* lowerMedia(part, request, adapter, "message");
|
|
401
|
+
if (lowered.type === "input_video")
|
|
402
|
+
return yield* ProviderShared.invalidRequest(`${adapter.name} user messages do not support input_video`);
|
|
403
|
+
return lowered;
|
|
404
|
+
});
|
|
405
|
+
// Tool results may carry structured text, images, and files. Keep media as provider-native
|
|
406
|
+
// content instead of JSON-stringifying base64 into a prompt string.
|
|
407
|
+
const lowerToolResultContentItem = Effect.fnUntraced(function* (item, request, adapter) {
|
|
408
|
+
if (item.type === "text")
|
|
409
|
+
return { type: "input_text", text: item.text };
|
|
410
|
+
return yield* lowerMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name }, request, adapter, "tool-result");
|
|
411
|
+
});
|
|
412
|
+
const lowerHostedToolResultContentItem = Effect.fnUntraced(function* (item, request, adapter) {
|
|
413
|
+
if (item.type === "text")
|
|
414
|
+
return { type: "input_text", text: item.text };
|
|
415
|
+
return yield* lowerMessageMedia({ type: "media", mediaType: item.mime, data: item.uri, filename: item.name }, request, adapter);
|
|
416
|
+
});
|
|
417
|
+
const lowerToolResultOutput = Effect.fnUntraced(function* (part, request, adapter) {
|
|
418
|
+
// Text/json/error results are encoded as a plain string for backward
|
|
419
|
+
// compatibility with existing cassettes and provider expectations.
|
|
420
|
+
if (part.result.type !== "content")
|
|
421
|
+
return ProviderShared.toolResultText(part);
|
|
422
|
+
// Preserve the narrowed array element type when compiled through a consumer package.
|
|
423
|
+
const content = part.result.value;
|
|
424
|
+
return yield* Effect.forEach(content, (item) => lowerToolResultContentItem(item, request, adapter));
|
|
425
|
+
});
|
|
426
|
+
const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (request, adapter) {
|
|
427
|
+
const input = [];
|
|
428
|
+
const providerMetadataKey = metadataKey(request.model);
|
|
429
|
+
for (const message of request.messages) {
|
|
430
|
+
const metadata = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Schema.UndefinedOr(MessageMetadata)))(message.providerMetadata?.[providerMetadataKey]);
|
|
431
|
+
if (message.role === "system") {
|
|
432
|
+
input.push({
|
|
433
|
+
role: "developer",
|
|
434
|
+
content: ProviderShared.joinText(yield* ProviderShared.systemUpdateText(adapter.name, message)),
|
|
435
|
+
});
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (message.role === "user") {
|
|
439
|
+
const content = yield* Effect.forEach(message.content, (part) => lowerUserContent(part, request, adapter));
|
|
440
|
+
if (content.length > 0)
|
|
441
|
+
input.push({ role: "user", content, type: metadata?.type, id: metadata?.itemId, status: metadata?.status });
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (message.role === "assistant") {
|
|
445
|
+
const content = [];
|
|
446
|
+
const reasoningItems = {};
|
|
447
|
+
const hostedToolItems = new Set();
|
|
448
|
+
const flushText = () => {
|
|
449
|
+
if (content.length === 0)
|
|
450
|
+
return;
|
|
451
|
+
const groups = content.reduce((groups, part) => {
|
|
452
|
+
const partMetadata = part.providerMetadata?.[providerMetadataKey];
|
|
453
|
+
const id = itemID(part.providerMetadata, providerMetadataKey) ?? metadata?.itemId;
|
|
454
|
+
const partPhase = messagePhase(partMetadata?.phase);
|
|
455
|
+
const phase = partPhase === undefined ? metadata?.phase : partPhase;
|
|
456
|
+
const group = groups.at(-1);
|
|
457
|
+
if (group && group.id === id && group.phase === phase)
|
|
458
|
+
group.parts.push(part);
|
|
459
|
+
else
|
|
460
|
+
groups.push({ id, phase, parts: [part] });
|
|
461
|
+
return groups;
|
|
462
|
+
}, []);
|
|
463
|
+
input.push(...groups.map((group) => ({
|
|
464
|
+
type: "message",
|
|
465
|
+
...(group.id === undefined ? {} : { id: group.id }),
|
|
466
|
+
role: "assistant",
|
|
467
|
+
// Replayed text is a finished input item, even if generation was cut short.
|
|
468
|
+
status: "completed",
|
|
469
|
+
content: group.parts.map((part) => ({ type: "output_text", text: part.text })),
|
|
470
|
+
...(group.phase === undefined ? {} : { phase: group.phase }),
|
|
471
|
+
})));
|
|
472
|
+
content.splice(0, content.length);
|
|
473
|
+
};
|
|
474
|
+
for (const part of message.content) {
|
|
475
|
+
if (part.type === "compaction") {
|
|
476
|
+
flushText();
|
|
477
|
+
if (part.provider !== request.model.provider || part.encrypted === undefined)
|
|
478
|
+
return yield* ProviderShared.invalidRequest("Compaction state must be replayed to its originating provider and API");
|
|
479
|
+
input.push({ type: "compaction", id: part.id, encrypted_content: part.encrypted });
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (part.type === "text") {
|
|
483
|
+
content.push(part);
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (part.type === "reasoning") {
|
|
487
|
+
flushText();
|
|
488
|
+
const reasoning = lowerReasoning(part, providerMetadataKey);
|
|
489
|
+
if (!reasoning)
|
|
490
|
+
continue;
|
|
491
|
+
const existing = reasoning.id === undefined ? undefined : reasoningItems[reasoning.id];
|
|
492
|
+
if (existing) {
|
|
493
|
+
existing.summary.push(...reasoning.summary);
|
|
494
|
+
if (typeof reasoning.encrypted_content === "string")
|
|
495
|
+
existing.encrypted_content = reasoning.encrypted_content;
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (reasoning.id !== undefined)
|
|
499
|
+
reasoningItems[reasoning.id] = reasoning;
|
|
500
|
+
input.push(reasoning);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (part.type === "tool-call") {
|
|
504
|
+
flushText();
|
|
505
|
+
if (part.providerExecuted === true)
|
|
506
|
+
continue;
|
|
507
|
+
input.push(lowerToolCall(part, providerMetadataKey));
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (part.type === "tool-result" && part.providerExecuted === true) {
|
|
511
|
+
flushText();
|
|
512
|
+
const id = itemID(part.providerMetadata, providerMetadataKey);
|
|
513
|
+
const hosted = part.result.type !== "json"
|
|
514
|
+
? undefined
|
|
515
|
+
: Schema.is(HostedToolItem)(part.result.value)
|
|
516
|
+
? part.result.value
|
|
517
|
+
: adapter.restoreHostedToolItem?.(part.result.value);
|
|
518
|
+
if (id !== undefined && hosted?.id === id) {
|
|
519
|
+
if (!hostedToolItems.has(id)) {
|
|
520
|
+
input.push(hosted);
|
|
521
|
+
hostedToolItems.add(id);
|
|
522
|
+
}
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const content = part.result.type === "content"
|
|
526
|
+
? part.result.value
|
|
527
|
+
: [{ type: "text", text: ProviderShared.toolResultText(part) }];
|
|
528
|
+
input.push({
|
|
529
|
+
role: "user",
|
|
530
|
+
content: yield* Effect.forEach(content, (item) => lowerHostedToolResultContentItem(item, request, adapter)),
|
|
531
|
+
});
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
return yield* ProviderShared.unsupportedContent(adapter.name, "assistant", [
|
|
535
|
+
"text",
|
|
536
|
+
"reasoning",
|
|
537
|
+
"tool-call",
|
|
538
|
+
"tool-result",
|
|
539
|
+
]);
|
|
540
|
+
}
|
|
541
|
+
flushText();
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
for (const part of message.content) {
|
|
545
|
+
if (!ProviderShared.supportsContent(part, ["tool-result"]))
|
|
546
|
+
return yield* ProviderShared.unsupportedContent(adapter.name, "tool", ["tool-result"]);
|
|
547
|
+
input.push({
|
|
548
|
+
type: "function_call_output",
|
|
549
|
+
call_id: part.id,
|
|
550
|
+
output: yield* lowerToolResultOutput(part, request, adapter),
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return input;
|
|
555
|
+
});
|
|
556
|
+
export const lowerConversation = Effect.fn("OpenResponses.lowerConversation")(function* (request, adapter) {
|
|
557
|
+
const instructions = ProviderShared.joinText(request.system);
|
|
558
|
+
return {
|
|
559
|
+
model: request.model.id,
|
|
560
|
+
input: yield* lowerMessages(request, adapter),
|
|
561
|
+
...(instructions ? { instructions } : {}),
|
|
562
|
+
};
|
|
563
|
+
});
|
|
564
|
+
export const lowerGeneration = (request) => {
|
|
565
|
+
const options = OpenResponsesOptions.resolve(request);
|
|
566
|
+
const generation = request.generation;
|
|
567
|
+
const cacheKey = ProviderShared.promptCacheKey(request);
|
|
568
|
+
const parallelToolCalls = resolveParallelToolCalls(request);
|
|
569
|
+
return {
|
|
570
|
+
stream: true,
|
|
571
|
+
max_output_tokens: generation?.maxTokens,
|
|
572
|
+
temperature: generation?.temperature,
|
|
573
|
+
top_p: generation?.topP,
|
|
574
|
+
presence_penalty: generation?.presencePenalty,
|
|
575
|
+
frequency_penalty: generation?.frequencyPenalty,
|
|
576
|
+
...(options.store !== undefined ? { store: options.store } : {}),
|
|
577
|
+
...(options.metadata ? { metadata: options.metadata } : {}),
|
|
578
|
+
...(options.safetyIdentifier ? { safety_identifier: options.safetyIdentifier } : {}),
|
|
579
|
+
...(options.streamOptions?.includeObfuscation !== undefined
|
|
580
|
+
? { stream_options: { include_obfuscation: options.streamOptions.includeObfuscation } }
|
|
581
|
+
: {}),
|
|
582
|
+
...(options.topLogprobs !== undefined ? { top_logprobs: options.topLogprobs } : {}),
|
|
583
|
+
...(cacheKey ? { prompt_cache_key: cacheKey } : {}),
|
|
584
|
+
...(options.include ? { include: options.include } : {}),
|
|
585
|
+
...(options.reasoningEffort || options.reasoningSummary
|
|
586
|
+
? { reasoning: { effort: options.reasoningEffort, summary: options.reasoningSummary } }
|
|
587
|
+
: {}),
|
|
588
|
+
...(options.textVerbosity ? { text: { verbosity: options.textVerbosity } } : {}),
|
|
589
|
+
...(options.serviceTier ? { service_tier: options.serviceTier } : {}),
|
|
590
|
+
...(options.maxToolCalls !== undefined ? { max_tool_calls: options.maxToolCalls } : {}),
|
|
591
|
+
...(parallelToolCalls !== undefined ? { parallel_tool_calls: parallelToolCalls } : {}),
|
|
592
|
+
...(options.truncation ? { truncation: options.truncation } : {}),
|
|
593
|
+
};
|
|
594
|
+
};
|
|
595
|
+
export const resolveParallelToolCalls = (request) => {
|
|
596
|
+
const configured = OpenResponsesOptions.resolve(request).parallelToolCalls;
|
|
597
|
+
if (configured !== undefined)
|
|
598
|
+
return configured;
|
|
599
|
+
const disabled = request.toolChoice?.disableParallelToolUse;
|
|
600
|
+
return disabled === undefined ? undefined : !disabled;
|
|
601
|
+
};
|
|
602
|
+
export const allowedToolChoice = (request) => {
|
|
603
|
+
const allowed = OpenResponsesOptions.resolve(request).allowedTools;
|
|
604
|
+
if (!allowed)
|
|
605
|
+
return undefined;
|
|
606
|
+
return {
|
|
607
|
+
type: "allowed_tools",
|
|
608
|
+
mode: allowed.mode,
|
|
609
|
+
tools: allowed.toolNames.map((name) => ({ type: "function", name })),
|
|
610
|
+
};
|
|
611
|
+
};
|
|
612
|
+
export const fromRequestWithAdapter = Effect.fn("OpenResponses.fromRequestWithAdapter")(function* (request, adapter) {
|
|
613
|
+
const projected = ProviderShared.flattenToolRequest(request);
|
|
614
|
+
const toolSchemaCompatibility = request.model.compatibility?.toolSchema;
|
|
615
|
+
return {
|
|
616
|
+
...(yield* lowerConversation(projected.request, adapter)),
|
|
617
|
+
...lowerGeneration(request),
|
|
618
|
+
tools: projected.tools.length === 0
|
|
619
|
+
? undefined
|
|
620
|
+
: yield* Effect.forEach(projected.tools, (tool) => tool.native !== undefined && adapter.nativeTool
|
|
621
|
+
? adapter.nativeTool(tool.native)
|
|
622
|
+
: lowerTool(adapter.name, tool, ToolSchemaProjection.modelCompatibility(tool.inputSchema, toolSchemaCompatibility))),
|
|
623
|
+
tool_choice: allowedToolChoice(request) ??
|
|
624
|
+
(request.toolChoice ? yield* lowerToolChoice(adapter.name, request.toolChoice) : undefined),
|
|
625
|
+
};
|
|
626
|
+
});
|
|
627
|
+
const decodeBody = ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenResponsesBody));
|
|
628
|
+
export const fromRequest = Effect.fn("OpenResponses.fromRequest")(function* (request) {
|
|
629
|
+
return yield* decodeBody(yield* fromRequestWithAdapter(request, BASE_ADAPTER));
|
|
630
|
+
});
|
|
631
|
+
// =============================================================================
|
|
632
|
+
// Stream Parsing
|
|
633
|
+
// =============================================================================
|
|
634
|
+
// Responses APIs report `input_tokens` (inclusive total) with a
|
|
635
|
+
// cached-read and cache-write subsets, and `output_tokens` (inclusive total)
|
|
636
|
+
// with a `reasoning_tokens` subset. Pass the totals through and derive the
|
|
637
|
+
// non-cached breakdown.
|
|
638
|
+
export const mapUsage = (usage, providerMetadataKey) => {
|
|
639
|
+
if (!usage)
|
|
640
|
+
return undefined;
|
|
641
|
+
const cached = usage.input_tokens_details?.cached_tokens;
|
|
642
|
+
const cacheWrite = usage.input_tokens_details?.cache_write_tokens;
|
|
643
|
+
const reasoning = usage.output_tokens_details?.reasoning_tokens;
|
|
644
|
+
const nonCached = ProviderShared.subtractTokens(usage.input_tokens, ProviderShared.sumTokens(cached, cacheWrite));
|
|
645
|
+
return new Usage({
|
|
646
|
+
inputTokens: usage.input_tokens,
|
|
647
|
+
outputTokens: usage.output_tokens,
|
|
648
|
+
nonCachedInputTokens: nonCached,
|
|
649
|
+
cacheReadInputTokens: cached,
|
|
650
|
+
cacheWriteInputTokens: cacheWrite,
|
|
651
|
+
reasoningTokens: reasoning,
|
|
652
|
+
totalTokens: ProviderShared.totalTokens(usage.input_tokens, usage.output_tokens, usage.total_tokens),
|
|
653
|
+
providerMetadata: { [providerMetadataKey]: usage },
|
|
654
|
+
});
|
|
655
|
+
};
|
|
656
|
+
const mapFinishReason = (event, hasFunctionCall) => {
|
|
657
|
+
const reason = event.response?.incomplete_details?.reason;
|
|
658
|
+
if (reason === undefined || reason === null) {
|
|
659
|
+
if (hasFunctionCall)
|
|
660
|
+
return "tool-calls";
|
|
661
|
+
if (event.type === "response.incomplete")
|
|
662
|
+
return "unknown";
|
|
663
|
+
return "stop";
|
|
664
|
+
}
|
|
665
|
+
if (reason === "max_output_tokens")
|
|
666
|
+
return "length";
|
|
667
|
+
if (reason === "content_filter")
|
|
668
|
+
return "content-filter";
|
|
669
|
+
return hasFunctionCall ? "tool-calls" : "unknown";
|
|
670
|
+
};
|
|
671
|
+
export const metadataKey = (model) => model.route.providerMetadataKey ?? "openresponses";
|
|
672
|
+
export const providerMetadata = (state, metadata) => ({
|
|
673
|
+
[state.providerMetadataKey]: metadata,
|
|
674
|
+
});
|
|
675
|
+
const NO_EVENTS = [];
|
|
676
|
+
// `response.completed` / `response.incomplete` are clean finishes that emit a
|
|
677
|
+
// `finish` event; `response.failed` and `error` are hard failures. All four end
|
|
678
|
+
// the stream, so keep this set aligned with `step` and the protocol's terminal predicate.
|
|
679
|
+
const TERMINAL_TYPES = new Set(["error", "response.completed", "response.incomplete", "response.failed"]);
|
|
680
|
+
export const terminal = (event) => TERMINAL_TYPES.has(event.type);
|
|
681
|
+
const onOutputTextDelta = (state, event, id) => {
|
|
682
|
+
if (!event.delta || state.message?.id !== id)
|
|
683
|
+
return [state, NO_EVENTS];
|
|
684
|
+
const events = [];
|
|
685
|
+
const phase = state.message.phase;
|
|
686
|
+
const metadata = providerMetadata(state, { itemId: id, ...(phase === undefined ? {} : { phase }) });
|
|
687
|
+
const lifecycle = Lifecycle.textStart(state.lifecycle, events, id, metadata);
|
|
688
|
+
return [{ ...state, lifecycle: Lifecycle.textDelta(lifecycle, events, id, event.delta) }, events];
|
|
689
|
+
};
|
|
690
|
+
const onOutputTextDone = (state, event, id) => {
|
|
691
|
+
if (state.message?.id === id) {
|
|
692
|
+
if (state.lifecycle.text.has(id) || event.text === undefined)
|
|
693
|
+
return [state, NO_EVENTS];
|
|
694
|
+
return onOutputTextDelta(state, { ...event, delta: event.text }, id);
|
|
695
|
+
}
|
|
696
|
+
const events = [];
|
|
697
|
+
return [{ ...state, lifecycle: Lifecycle.textEnd(state.lifecycle, events, id) }, events];
|
|
698
|
+
};
|
|
699
|
+
const decodeMessagePart = Schema.decodeUnknownOption(Schema.Union([OpenResponsesOutputText, Schema.Struct({ type: Schema.tag("refusal"), refusal: Schema.String })]));
|
|
700
|
+
const decodeSummaryPart = Schema.decodeUnknownOption(OpenResponsesReasoningSummaryText);
|
|
701
|
+
const decodeReasoningPart = Schema.decodeUnknownOption(Schema.Struct({ type: Schema.tag("reasoning_text"), text: Schema.String }));
|
|
702
|
+
const joinReasoningText = (parts) => {
|
|
703
|
+
if (!parts.some((part) => part !== undefined && part.length > 0))
|
|
704
|
+
return undefined;
|
|
705
|
+
return parts.filter((part) => part !== undefined).join("\n\n");
|
|
706
|
+
};
|
|
707
|
+
const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);
|
|
708
|
+
const ITEM_ID_PREFIX = {
|
|
709
|
+
message: "msg",
|
|
710
|
+
reasoning: "rs",
|
|
711
|
+
function_call: "fc",
|
|
712
|
+
compaction: "cmp",
|
|
713
|
+
};
|
|
714
|
+
// An item without an id adopts the id already open in its output slot,
|
|
715
|
+
// otherwise it gets a locally minted one.
|
|
716
|
+
const resolveItem = (state, item, index) => ({
|
|
717
|
+
...item,
|
|
718
|
+
id: item.id ??
|
|
719
|
+
(index === undefined ? undefined : state.outputItems[index]) ??
|
|
720
|
+
`${ITEM_ID_PREFIX[item.type] ?? "item"}_${crypto.randomUUID().replaceAll("-", "")}`,
|
|
721
|
+
});
|
|
722
|
+
// Registered output slots are authoritative for `item_id` routing, and items
|
|
723
|
+
// are resolved here so everything downstream can rely on `item.id`.
|
|
724
|
+
export const normalize = (state, input) => ({
|
|
725
|
+
...input,
|
|
726
|
+
item_id: input.item_id === undefined ? undefined : outputItemID(state, input),
|
|
727
|
+
item: input.item ? resolveItem(state, input.item, input.output_index) : input.item,
|
|
728
|
+
});
|
|
729
|
+
const startReasoningSummaryPart = (state, itemID, index) => {
|
|
730
|
+
const item = state.reasoningItems[itemID];
|
|
731
|
+
if (!item || index === 0 || item.summaryParts[index] !== undefined)
|
|
732
|
+
return [state, NO_EVENTS];
|
|
733
|
+
const events = [];
|
|
734
|
+
const lifecycle = Object.entries(item.summaryParts)
|
|
735
|
+
.filter((entry) => entry[1] !== "concluded")
|
|
736
|
+
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${itemID}:${entry[0]}`, providerMetadata(state, { itemId: itemID })), state.lifecycle);
|
|
737
|
+
return [
|
|
738
|
+
{
|
|
739
|
+
...state,
|
|
740
|
+
lifecycle: Lifecycle.reasoningStart(lifecycle, events, `${itemID}:${index}`, providerMetadata(state, { itemId: itemID, reasoningEncryptedContent: item.encryptedContent ?? null })),
|
|
741
|
+
reasoningItems: {
|
|
742
|
+
...state.reasoningItems,
|
|
743
|
+
[itemID]: {
|
|
744
|
+
...item,
|
|
745
|
+
summaryParts: {
|
|
746
|
+
...Object.fromEntries(Object.entries(item.summaryParts).map((entry) => entry[1] === "concluded" ? entry : [entry[0], "concluded"])),
|
|
747
|
+
[index]: "active",
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
events,
|
|
753
|
+
];
|
|
754
|
+
};
|
|
755
|
+
export const onReasoningDelta = (state, event, itemID) => {
|
|
756
|
+
const item = state.reasoningItems[itemID];
|
|
757
|
+
if (!event.delta || !item)
|
|
758
|
+
return [state, NO_EVENTS];
|
|
759
|
+
const index = event.summary_index ?? 0;
|
|
760
|
+
if (item.summaryParts[index] === "concluded")
|
|
761
|
+
return [state, NO_EVENTS];
|
|
762
|
+
const [started, emitted] = startReasoningSummaryPart(state, itemID, index);
|
|
763
|
+
const current = started.reasoningItems[itemID];
|
|
764
|
+
if (!current)
|
|
765
|
+
return [started, emitted];
|
|
766
|
+
const events = [...emitted];
|
|
767
|
+
return [
|
|
768
|
+
{
|
|
769
|
+
...started,
|
|
770
|
+
lifecycle: Lifecycle.reasoningDelta(started.lifecycle, events, `${itemID}:${index}`, event.delta),
|
|
771
|
+
reasoningItems: {
|
|
772
|
+
...started.reasoningItems,
|
|
773
|
+
[itemID]: { ...current, deltaIndexes: new Set([...current.deltaIndexes, index]) },
|
|
774
|
+
},
|
|
775
|
+
},
|
|
776
|
+
events,
|
|
777
|
+
];
|
|
778
|
+
};
|
|
779
|
+
// Some compatible gateways emit a reasoning final without streaming any
|
|
780
|
+
// deltas, mirroring `response.output_text.done`. Reconcile the complete text
|
|
781
|
+
// as a single delta unless that summary index already streamed one.
|
|
782
|
+
export const onReasoningDone = (state, event, itemID) => {
|
|
783
|
+
const item = state.reasoningItems[itemID];
|
|
784
|
+
if (!item || typeof event.text !== "string")
|
|
785
|
+
return [state, NO_EVENTS];
|
|
786
|
+
const index = event.summary_index ?? 0;
|
|
787
|
+
if (item.deltaIndexes.has(index))
|
|
788
|
+
return [state, NO_EVENTS];
|
|
789
|
+
return onReasoningDelta(state, { ...event, delta: event.text }, itemID);
|
|
790
|
+
};
|
|
791
|
+
const reasoningMetadata = (state, item) => providerMetadata(state, { itemId: item.id, reasoningEncryptedContent: item.encrypted_content ?? null });
|
|
792
|
+
// Responses APIs normally stream reasoning items in this order:
|
|
793
|
+
// `output_item.added` (reasoning) →
|
|
794
|
+
// `reasoning_summary_part.added` (index=0) →
|
|
795
|
+
// `reasoning_summary_text.delta` →
|
|
796
|
+
// `reasoning_summary_part.done` (index=0) →
|
|
797
|
+
// (repeat for index>0) →
|
|
798
|
+
// `output_item.done` (reasoning).
|
|
799
|
+
// `onOutputItemAdded` seeds the per-item entry, while each later part start is
|
|
800
|
+
// also an implicit boundary for the previous part. This keeps the common event
|
|
801
|
+
// lifecycle ordered when a compatible provider omits or delays a part-done event.
|
|
802
|
+
const onOutputItemAdded = (state, event) => {
|
|
803
|
+
const item = event.item;
|
|
804
|
+
if (!item)
|
|
805
|
+
return [state, NO_EVENTS];
|
|
806
|
+
if (item.type === "message") {
|
|
807
|
+
const phase = messagePhase(item.phase);
|
|
808
|
+
// A new message closes earlier messages, including ones that never streamed.
|
|
809
|
+
const events = [];
|
|
810
|
+
const lifecycle = [...state.lifecycle.text]
|
|
811
|
+
.filter((id) => id !== item.id)
|
|
812
|
+
.reduce((lifecycle, id) => {
|
|
813
|
+
const openPhase = state.message?.id === id ? state.message.phase : undefined;
|
|
814
|
+
return Lifecycle.textEnd(lifecycle, events, id, providerMetadata(state, { itemId: id, ...(openPhase === undefined ? {} : { phase: openPhase }) }));
|
|
815
|
+
}, state.lifecycle);
|
|
816
|
+
return [
|
|
817
|
+
{
|
|
818
|
+
...state,
|
|
819
|
+
lifecycle,
|
|
820
|
+
message: {
|
|
821
|
+
id: item.id,
|
|
822
|
+
phase: phase === undefined && state.message?.id === item.id ? state.message.phase : phase,
|
|
823
|
+
},
|
|
824
|
+
},
|
|
825
|
+
events,
|
|
826
|
+
];
|
|
827
|
+
}
|
|
828
|
+
if (item.type === "reasoning") {
|
|
829
|
+
if (state.reasoningItems[item.id] !== undefined)
|
|
830
|
+
return [state, NO_EVENTS];
|
|
831
|
+
const events = [];
|
|
832
|
+
return [
|
|
833
|
+
{
|
|
834
|
+
...state,
|
|
835
|
+
lifecycle: Lifecycle.reasoningStart(state.lifecycle, events, `${item.id}:0`, reasoningMetadata(state, item)),
|
|
836
|
+
reasoningItems: {
|
|
837
|
+
...state.reasoningItems,
|
|
838
|
+
[item.id]: {
|
|
839
|
+
encryptedContent: item.encrypted_content,
|
|
840
|
+
summaryParts: { 0: "active" },
|
|
841
|
+
deltaIndexes: new Set(),
|
|
842
|
+
},
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
events,
|
|
846
|
+
];
|
|
847
|
+
}
|
|
848
|
+
if (item.type !== "function_call" || !item.call_id)
|
|
849
|
+
return [state, NO_EVENTS];
|
|
850
|
+
if (state.tools[item.id] !== undefined)
|
|
851
|
+
return [state, NO_EVENTS];
|
|
852
|
+
const metadata = providerMetadata(state, { itemId: item.id });
|
|
853
|
+
const events = [];
|
|
854
|
+
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
|
|
855
|
+
return [
|
|
856
|
+
{
|
|
857
|
+
...state,
|
|
858
|
+
lifecycle,
|
|
859
|
+
tools: ToolStream.start(state.tools, item.id, {
|
|
860
|
+
id: item.call_id,
|
|
861
|
+
name: item.name ?? "",
|
|
862
|
+
namespace: item.namespace,
|
|
863
|
+
input: item.arguments ?? "",
|
|
864
|
+
providerMetadata: metadata,
|
|
865
|
+
}),
|
|
866
|
+
},
|
|
867
|
+
[
|
|
868
|
+
...events,
|
|
869
|
+
LLMEvent.toolInputStart({
|
|
870
|
+
id: item.call_id,
|
|
871
|
+
name: item.name ?? "",
|
|
872
|
+
namespace: item.namespace,
|
|
873
|
+
providerMetadata: metadata,
|
|
874
|
+
}),
|
|
875
|
+
],
|
|
876
|
+
];
|
|
877
|
+
};
|
|
878
|
+
const onReasoningSummaryPartAdded = (state, event) => {
|
|
879
|
+
if (event.item_id === undefined || event.summary_index === undefined)
|
|
880
|
+
return [state, NO_EVENTS];
|
|
881
|
+
return startReasoningSummaryPart(state, event.item_id, event.summary_index);
|
|
882
|
+
};
|
|
883
|
+
const onReasoningSummaryPartDone = (state, event) => {
|
|
884
|
+
if (event.item_id === undefined || event.summary_index === undefined)
|
|
885
|
+
return [state, NO_EVENTS];
|
|
886
|
+
const item = state.reasoningItems[event.item_id];
|
|
887
|
+
if (!item)
|
|
888
|
+
return [state, NO_EVENTS];
|
|
889
|
+
if (item.summaryParts[event.summary_index] !== "active")
|
|
890
|
+
return [state, NO_EVENTS];
|
|
891
|
+
return [
|
|
892
|
+
{
|
|
893
|
+
...state,
|
|
894
|
+
reasoningItems: {
|
|
895
|
+
...state.reasoningItems,
|
|
896
|
+
[event.item_id]: {
|
|
897
|
+
...item,
|
|
898
|
+
summaryParts: {
|
|
899
|
+
...item.summaryParts,
|
|
900
|
+
[event.summary_index]: "can-conclude",
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
},
|
|
904
|
+
},
|
|
905
|
+
NO_EVENTS,
|
|
906
|
+
];
|
|
907
|
+
};
|
|
908
|
+
const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgumentsDelta")(function* (state, event) {
|
|
909
|
+
if (event.item_id === undefined)
|
|
910
|
+
return [state, NO_EVENTS];
|
|
911
|
+
const tool = state.tools[event.item_id];
|
|
912
|
+
if (!tool)
|
|
913
|
+
return [state, NO_EVENTS];
|
|
914
|
+
const final = event.type === "response.function_call_arguments.done" ? event.arguments : undefined;
|
|
915
|
+
if (event.type === "response.function_call_arguments.done" && final === undefined)
|
|
916
|
+
return [state, NO_EVENTS];
|
|
917
|
+
if (final !== undefined && !final.startsWith(tool.input))
|
|
918
|
+
return [
|
|
919
|
+
{ ...state, tools: ToolStream.start(state.tools, event.item_id, { ...tool, input: final }) },
|
|
920
|
+
NO_EVENTS,
|
|
921
|
+
];
|
|
922
|
+
const delta = final === undefined ? event.delta : final.slice(tool.input.length);
|
|
923
|
+
if (!delta)
|
|
924
|
+
return [state, NO_EVENTS];
|
|
925
|
+
const result = ToolStream.appendExisting(state.id, state.tools, event.item_id, delta, `${state.name} tool argument delta is missing its tool call`);
|
|
926
|
+
if (ToolStream.isError(result))
|
|
927
|
+
return yield* result;
|
|
928
|
+
const events = [];
|
|
929
|
+
const lifecycle = result.events.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
|
|
930
|
+
events.push(...result.events);
|
|
931
|
+
return [{ ...state, lifecycle, tools: result.tools }, events];
|
|
932
|
+
});
|
|
933
|
+
const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (state, item) {
|
|
934
|
+
if (!item)
|
|
935
|
+
return [state, NO_EVENTS];
|
|
936
|
+
if (item.type === "compaction") {
|
|
937
|
+
if (typeof item.encrypted_content !== "string")
|
|
938
|
+
return yield* ProviderShared.eventError(state.id, "Compaction output is missing its encrypted content");
|
|
939
|
+
if (state.completedCompactions.has(item.id))
|
|
940
|
+
return [state, NO_EVENTS];
|
|
941
|
+
const events = [];
|
|
942
|
+
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
|
|
943
|
+
events.push(LLMEvent.compaction({
|
|
944
|
+
provider: state.provider,
|
|
945
|
+
id: item.id,
|
|
946
|
+
encrypted: item.encrypted_content,
|
|
947
|
+
}));
|
|
948
|
+
return [
|
|
949
|
+
{ ...state, lifecycle, completedCompactions: new Set([...state.completedCompactions, item.id]) },
|
|
950
|
+
events,
|
|
951
|
+
];
|
|
952
|
+
}
|
|
953
|
+
if (item.type === "message") {
|
|
954
|
+
const active = state.message?.id === item.id;
|
|
955
|
+
const itemPhase = messagePhase(item.phase);
|
|
956
|
+
const phase = itemPhase === undefined && active ? state.message?.phase : itemPhase;
|
|
957
|
+
const parts = Array.isArray(item.content) ? item.content : [];
|
|
958
|
+
const content = [];
|
|
959
|
+
for (const part of parts) {
|
|
960
|
+
const decoded = Option.getOrUndefined(decodeMessagePart(part));
|
|
961
|
+
if (!decoded)
|
|
962
|
+
continue;
|
|
963
|
+
content.push(decoded.type === "output_text" ? decoded.text : decoded.refusal);
|
|
964
|
+
}
|
|
965
|
+
const text = content.length > 0 ? content.join("") : undefined;
|
|
966
|
+
const metadata = providerMetadata(state, { itemId: item.id, ...(phase === undefined ? {} : { phase }) });
|
|
967
|
+
const events = [];
|
|
968
|
+
const lifecycle = text ? Lifecycle.textStart(state.lifecycle, events, item.id, metadata) : state.lifecycle;
|
|
969
|
+
return [
|
|
970
|
+
{
|
|
971
|
+
...state,
|
|
972
|
+
lifecycle: Lifecycle.textEnd(lifecycle, events, item.id, metadata, text),
|
|
973
|
+
message: active ? undefined : state.message,
|
|
974
|
+
},
|
|
975
|
+
events,
|
|
976
|
+
];
|
|
977
|
+
}
|
|
978
|
+
if (item.type === "function_call") {
|
|
979
|
+
if (!item.call_id || !item.name)
|
|
980
|
+
return [state, NO_EVENTS];
|
|
981
|
+
const metadata = providerMetadata(state, { itemId: item.id });
|
|
982
|
+
const registered = state.tools[item.id] !== undefined;
|
|
983
|
+
const tools = registered
|
|
984
|
+
? state.tools
|
|
985
|
+
: ToolStream.start(state.tools, item.id, {
|
|
986
|
+
id: item.call_id,
|
|
987
|
+
name: item.name,
|
|
988
|
+
namespace: item.namespace,
|
|
989
|
+
providerMetadata: metadata,
|
|
990
|
+
});
|
|
991
|
+
const result = item.arguments === undefined
|
|
992
|
+
? yield* ToolStream.finish(state.id, tools, item.id)
|
|
993
|
+
: yield* ToolStream.finishWithInput(state.id, tools, item.id, item.arguments);
|
|
994
|
+
const events = [];
|
|
995
|
+
const finished = result.events ?? [];
|
|
996
|
+
// A done-only call never streamed a start event, so open its lifecycle here.
|
|
997
|
+
const resultEvents = registered || finished.length === 0
|
|
998
|
+
? finished
|
|
999
|
+
: [
|
|
1000
|
+
LLMEvent.toolInputStart({
|
|
1001
|
+
id: item.call_id,
|
|
1002
|
+
name: item.name,
|
|
1003
|
+
namespace: item.namespace,
|
|
1004
|
+
providerMetadata: metadata,
|
|
1005
|
+
}),
|
|
1006
|
+
...finished,
|
|
1007
|
+
];
|
|
1008
|
+
const lifecycle = resultEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
|
|
1009
|
+
events.push(...resultEvents);
|
|
1010
|
+
return [
|
|
1011
|
+
{
|
|
1012
|
+
...state,
|
|
1013
|
+
lifecycle,
|
|
1014
|
+
hasFunctionCall: resultEvents.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
|
|
1015
|
+
state.hasFunctionCall,
|
|
1016
|
+
tools: result.tools,
|
|
1017
|
+
},
|
|
1018
|
+
events,
|
|
1019
|
+
];
|
|
1020
|
+
}
|
|
1021
|
+
if (item.type === "reasoning") {
|
|
1022
|
+
const metadata = reasoningMetadata(state, item);
|
|
1023
|
+
const summaryParts = Array.isArray(item.summary) ? item.summary : [];
|
|
1024
|
+
const summary = [];
|
|
1025
|
+
for (const part of summaryParts) {
|
|
1026
|
+
const decoded = Option.getOrUndefined(decodeSummaryPart(part));
|
|
1027
|
+
// Keep missing entries so the array still matches the provider's summary indexes.
|
|
1028
|
+
summary.push(decoded?.text);
|
|
1029
|
+
}
|
|
1030
|
+
const reasoningParts = Array.isArray(item.content) ? item.content : [];
|
|
1031
|
+
const content = [];
|
|
1032
|
+
for (const part of reasoningParts) {
|
|
1033
|
+
const decoded = Option.getOrUndefined(decodeReasoningPart(part));
|
|
1034
|
+
if (decoded)
|
|
1035
|
+
content.push(decoded.text);
|
|
1036
|
+
}
|
|
1037
|
+
const itemText = joinReasoningText(summary) ?? joinReasoningText(content);
|
|
1038
|
+
const events = [];
|
|
1039
|
+
const reasoningItem = state.reasoningItems[item.id];
|
|
1040
|
+
if (reasoningItem) {
|
|
1041
|
+
const fragments = Object.entries(reasoningItem.summaryParts);
|
|
1042
|
+
let lifecycle = state.lifecycle;
|
|
1043
|
+
for (const [index, status] of fragments) {
|
|
1044
|
+
if (status === "concluded")
|
|
1045
|
+
continue;
|
|
1046
|
+
// Do not repeat earlier summaries that were already emitted as separate fragments.
|
|
1047
|
+
const finalText = fragments.length === 1 ? itemText : summary[Number(index)];
|
|
1048
|
+
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${index}`, metadata, finalText || undefined);
|
|
1049
|
+
}
|
|
1050
|
+
const reasoningItems = { ...state.reasoningItems };
|
|
1051
|
+
delete reasoningItems[item.id];
|
|
1052
|
+
return [{ ...state, lifecycle, reasoningItems }, events];
|
|
1053
|
+
}
|
|
1054
|
+
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
|
|
1055
|
+
events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata: metadata }));
|
|
1056
|
+
events.push(LLMEvent.reasoningEnd({ id: item.id, providerMetadata: metadata, text: itemText }));
|
|
1057
|
+
return [{ ...state, lifecycle }, events];
|
|
1058
|
+
}
|
|
1059
|
+
return [state, NO_EVENTS];
|
|
1060
|
+
});
|
|
1061
|
+
const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (state, event) {
|
|
1062
|
+
let current = state;
|
|
1063
|
+
const events = [];
|
|
1064
|
+
if (event.type === "response.completed") {
|
|
1065
|
+
// An output item's array position is its output index.
|
|
1066
|
+
for (const item of (event.response?.output ?? []).map((item, index) => resolveItem(state, item, index))) {
|
|
1067
|
+
// Terminal recovery cannot insert a checkpoint before already-emitted content.
|
|
1068
|
+
if (item.type === "compaction" && state.lifecycle.stepStarted && !state.completedCompactions.has(item.id))
|
|
1069
|
+
return yield* ProviderShared.eventError(state.id, "Cannot recover a compaction checkpoint after output has been emitted");
|
|
1070
|
+
const recoverable = item.type === "compaction" || (item.type === "function_call" && current.tools[item.id] !== undefined);
|
|
1071
|
+
if (!recoverable)
|
|
1072
|
+
continue;
|
|
1073
|
+
const [next, emitted] = yield* onOutputItemDone(current, item);
|
|
1074
|
+
current = next;
|
|
1075
|
+
events.push(...emitted);
|
|
1076
|
+
}
|
|
1077
|
+
// Some compatible providers omit output_item.done even after completing the response.
|
|
1078
|
+
const pending = yield* ToolStream.finishAll(current.id, current.tools);
|
|
1079
|
+
current = {
|
|
1080
|
+
...current,
|
|
1081
|
+
tools: pending.tools,
|
|
1082
|
+
hasFunctionCall: current.hasFunctionCall ||
|
|
1083
|
+
pending.events.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)),
|
|
1084
|
+
};
|
|
1085
|
+
events.push(...pending.events);
|
|
1086
|
+
}
|
|
1087
|
+
const lifecycle = Lifecycle.finish(current.lifecycle, events, {
|
|
1088
|
+
reason: {
|
|
1089
|
+
normalized: mapFinishReason(event, current.hasFunctionCall),
|
|
1090
|
+
raw: event.response?.incomplete_details?.reason,
|
|
1091
|
+
},
|
|
1092
|
+
usage: mapUsage(event.response?.usage, current.providerMetadataKey),
|
|
1093
|
+
providerMetadata: event.response?.id || event.response?.service_tier
|
|
1094
|
+
? providerMetadata(current, {
|
|
1095
|
+
responseId: event.response.id,
|
|
1096
|
+
serviceTier: event.response.service_tier,
|
|
1097
|
+
})
|
|
1098
|
+
: undefined,
|
|
1099
|
+
});
|
|
1100
|
+
return [{ ...current, lifecycle }, events];
|
|
1101
|
+
});
|
|
1102
|
+
// Build the prettiest summary available from whatever the provider supplied.
|
|
1103
|
+
// When both code and message are present, prefix the code so consumers see
|
|
1104
|
+
// the failure mode (e.g. `rate_limit_exceeded: Slow down`) instead of just
|
|
1105
|
+
// the bare message — production rate limits and context-length failures used
|
|
1106
|
+
// to be indistinguishable from generic stream drops. Returns undefined when
|
|
1107
|
+
// the payload carries no usable summary.
|
|
1108
|
+
const providerErrorMessage = (event, nested) => {
|
|
1109
|
+
const message = event.message || nested?.message || undefined;
|
|
1110
|
+
const code = event.code || nested?.code || undefined;
|
|
1111
|
+
if (message && code)
|
|
1112
|
+
return `${code}: ${message}`;
|
|
1113
|
+
return message || code;
|
|
1114
|
+
};
|
|
1115
|
+
export const providerFailure = (event, fallback, body = ProviderShared.encodeJson(event)) => {
|
|
1116
|
+
const nested = event.error ?? event.response?.error ?? undefined;
|
|
1117
|
+
const summary = providerErrorMessage(event, nested);
|
|
1118
|
+
const message = summary ?? (body === "{}" ? fallback : body);
|
|
1119
|
+
const status = typeof event.status === "number"
|
|
1120
|
+
? event.status
|
|
1121
|
+
: typeof event.status_code === "number"
|
|
1122
|
+
? event.status_code
|
|
1123
|
+
: undefined;
|
|
1124
|
+
const reason = event.type === "error" &&
|
|
1125
|
+
event.error === undefined &&
|
|
1126
|
+
event.response === undefined &&
|
|
1127
|
+
summary === undefined &&
|
|
1128
|
+
status === undefined
|
|
1129
|
+
? new ProviderInternalError({ message, body })
|
|
1130
|
+
: classifyProviderFailure({ message, status, rawBody: body });
|
|
1131
|
+
return new AIError({ reason });
|
|
1132
|
+
};
|
|
1133
|
+
// Callers must pass events through `normalize` first. The OpenAPI requires
|
|
1134
|
+
// string IDs but imposes no minLength; empty is not missing.
|
|
1135
|
+
export const step = (state, event) => {
|
|
1136
|
+
if (event.type === "response.output_text.delta" || event.type === "response.output_text.done") {
|
|
1137
|
+
if (event.item_id === undefined)
|
|
1138
|
+
return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
|
|
1139
|
+
return Effect.succeed(event.type === "response.output_text.delta"
|
|
1140
|
+
? onOutputTextDelta(state, event, event.item_id)
|
|
1141
|
+
: onOutputTextDone(state, event, event.item_id));
|
|
1142
|
+
}
|
|
1143
|
+
if (event.type === "response.refusal.delta" || event.type === "response.refusal.done") {
|
|
1144
|
+
const value = event.type === "response.refusal.delta" ? event.delta : event.refusal;
|
|
1145
|
+
if (event.item_id === undefined || typeof value !== "string")
|
|
1146
|
+
return ProviderShared.eventError(state.id, `${event.type} is malformed`);
|
|
1147
|
+
return Effect.succeed(event.type === "response.refusal.delta"
|
|
1148
|
+
? onOutputTextDelta(state, event, event.item_id)
|
|
1149
|
+
: onOutputTextDone(state, { ...event, text: value }, event.item_id));
|
|
1150
|
+
}
|
|
1151
|
+
if (event.type === "response.reasoning.delta" || event.type === "response.reasoning_summary_text.delta") {
|
|
1152
|
+
if (event.item_id === undefined)
|
|
1153
|
+
return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
|
|
1154
|
+
return Effect.succeed(onReasoningDelta(state, event, event.item_id));
|
|
1155
|
+
}
|
|
1156
|
+
if (event.type === "response.reasoning.done" ||
|
|
1157
|
+
event.type === "response.reasoning_summary_text.done" ||
|
|
1158
|
+
event.type === "response.reasoning_text.done") {
|
|
1159
|
+
if (event.item_id === undefined)
|
|
1160
|
+
return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
|
|
1161
|
+
return Effect.succeed(onReasoningDone(state, event, event.item_id));
|
|
1162
|
+
}
|
|
1163
|
+
if (event.type === "response.reasoning_summary_part.added")
|
|
1164
|
+
return event.item_id !== undefined
|
|
1165
|
+
? Effect.succeed(onReasoningSummaryPartAdded(state, event))
|
|
1166
|
+
: ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
|
|
1167
|
+
if (event.type === "response.reasoning_summary_part.done")
|
|
1168
|
+
return event.item_id !== undefined
|
|
1169
|
+
? Effect.succeed(onReasoningSummaryPartDone(state, event))
|
|
1170
|
+
: ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
|
|
1171
|
+
if (event.type === "response.output_item.added") {
|
|
1172
|
+
if (event.item?.type === "reasoning" &&
|
|
1173
|
+
state.reasoningItems[event.item.id] === undefined &&
|
|
1174
|
+
state.lifecycle.reasoning.size > 0)
|
|
1175
|
+
return ProviderShared.eventError(state.id, `${event.type} started reasoning before the previous item ended`);
|
|
1176
|
+
return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && event.item
|
|
1177
|
+
? { ...state, outputItems: { ...state.outputItems, [event.output_index]: event.item.id } }
|
|
1178
|
+
: state, event));
|
|
1179
|
+
}
|
|
1180
|
+
if (event.type === "response.function_call_arguments.delta" || event.type === "response.function_call_arguments.done")
|
|
1181
|
+
return event.item_id !== undefined
|
|
1182
|
+
? onFunctionCallArgumentsDelta(state, event)
|
|
1183
|
+
: ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
|
|
1184
|
+
if (event.type === "response.output_item.done")
|
|
1185
|
+
return onOutputItemDone(state, event.item);
|
|
1186
|
+
if (event.type === "response.completed" || event.type === "response.incomplete")
|
|
1187
|
+
return onResponseFinish(state, event);
|
|
1188
|
+
if (event.type === "response.failed")
|
|
1189
|
+
return providerFailure(event, `${state.name} response failed`);
|
|
1190
|
+
if (event.type === "error")
|
|
1191
|
+
return decodeKnownErrorEvent(event).pipe(Effect.mapError((cause) => ProviderShared.eventError(state.id, `${state.name} returned a malformed error event`, ProviderShared.encodeJson(event), cause)), Effect.flatMap(() => providerFailure(event, `${state.name} stream error`)));
|
|
1192
|
+
return Effect.succeed([state, NO_EVENTS]);
|
|
1193
|
+
};
|
|
1194
|
+
// =============================================================================
|
|
1195
|
+
// Protocol
|
|
1196
|
+
// =============================================================================
|
|
1197
|
+
/**
|
|
1198
|
+
* The provider-neutral Open Responses protocol. Provider-specific Responses
|
|
1199
|
+
* implementations compose this baseline with their own tools and event variants.
|
|
1200
|
+
*/
|
|
1201
|
+
export const initial = (request, adapter = BASE_ADAPTER) => ({
|
|
1202
|
+
provider: request.model.provider,
|
|
1203
|
+
completedCompactions: new Set(),
|
|
1204
|
+
id: adapter.id,
|
|
1205
|
+
name: adapter.name,
|
|
1206
|
+
providerMetadataKey: metadataKey(request.model),
|
|
1207
|
+
hasFunctionCall: false,
|
|
1208
|
+
tools: ToolStream.empty(),
|
|
1209
|
+
lifecycle: Lifecycle.initial(),
|
|
1210
|
+
outputItems: {},
|
|
1211
|
+
message: undefined,
|
|
1212
|
+
reasoningItems: {},
|
|
1213
|
+
});
|
|
1214
|
+
export const protocol = Protocol.make({
|
|
1215
|
+
id: ADAPTER,
|
|
1216
|
+
body: {
|
|
1217
|
+
schema: OpenResponsesBody,
|
|
1218
|
+
from: fromRequest,
|
|
1219
|
+
},
|
|
1220
|
+
stream: {
|
|
1221
|
+
event: Protocol.jsonEvent(Event),
|
|
1222
|
+
initial,
|
|
1223
|
+
step: (state, event) => step(state, normalize(state, event)),
|
|
1224
|
+
terminal,
|
|
1225
|
+
},
|
|
1226
|
+
});
|
|
1227
|
+
export const httpTransport = HttpTransport.sseJson.with();
|
|
1228
|
+
export * as OpenResponses from "./open-responses.js";
|