@yolk-sdk/agent 0.1.0-canary.59 → 0.1.0-canary.60
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 +49 -36
- package/dist/compaction/checkpoint.d.mts +2 -2
- package/dist/compaction/checkpoint.d.mts.map +1 -1
- package/dist/providers/openai/provider.d.mts +18 -5
- package/dist/providers/openai/provider.d.mts.map +1 -1
- package/dist/providers/openai/provider.mjs +79 -47
- package/dist/providers/openai/provider.mjs.map +1 -1
- package/dist/providers/vercel/ai-gateway-provider.d.mts +25 -0
- package/dist/providers/vercel/ai-gateway-provider.d.mts.map +1 -0
- package/dist/providers/vercel/ai-gateway-provider.mjs +39 -0
- package/dist/providers/vercel/ai-gateway-provider.mjs.map +1 -0
- package/dist/react/chat-messages.d.mts +1 -1
- package/dist/react/chat-messages.d.mts.map +1 -1
- package/dist/react/use-agent-chat.d.mts +1 -1
- package/dist/skillset/merge.d.mts +1 -1
- package/package.json +6 -1
- package/src/providers/openai/provider.ts +157 -60
- package/src/providers/vercel/ai-gateway-provider.ts +69 -0
package/README.md
CHANGED
|
@@ -17,36 +17,37 @@ Published package metadata requires Node.js 22+.
|
|
|
17
17
|
|
|
18
18
|
## Subpaths
|
|
19
19
|
|
|
20
|
-
| Subpath
|
|
21
|
-
|
|
|
22
|
-
| `@yolk-sdk/agent/protocol`
|
|
23
|
-
| `@yolk-sdk/agent/loop`
|
|
24
|
-
| `@yolk-sdk/agent/loop/testing`
|
|
25
|
-
| `@yolk-sdk/agent/runtime`
|
|
26
|
-
| `@yolk-sdk/agent/client`
|
|
27
|
-
| `@yolk-sdk/agent/compaction`
|
|
28
|
-
| `@yolk-sdk/agent/tools`
|
|
29
|
-
| `@yolk-sdk/agent/react`
|
|
30
|
-
| `@yolk-sdk/agent/oauth`
|
|
31
|
-
| `@yolk-sdk/agent/providers/openai`
|
|
32
|
-
| `@yolk-sdk/agent/providers/openai/codex`
|
|
33
|
-
| `@yolk-sdk/agent/providers/openai/codex-usage`
|
|
34
|
-
| `@yolk-sdk/agent/providers/openai/codex-provider`
|
|
35
|
-
| `@yolk-sdk/agent/providers/openai/provider`
|
|
36
|
-
| `@yolk-sdk/agent/providers/openai/realtime`
|
|
37
|
-
| `@yolk-sdk/agent/providers/openai/speech`
|
|
38
|
-
| `@yolk-sdk/agent/providers/
|
|
39
|
-
| `@yolk-sdk/agent/providers/anthropic
|
|
40
|
-
| `@yolk-sdk/agent/providers/anthropic/
|
|
41
|
-
| `@yolk-sdk/agent/providers/anthropic/
|
|
42
|
-
| `@yolk-sdk/agent/providers/
|
|
43
|
-
| `@yolk-sdk/agent/providers/xai
|
|
44
|
-
| `@yolk-sdk/agent/providers/xai/grok
|
|
45
|
-
| `@yolk-sdk/agent/providers/
|
|
46
|
-
| `@yolk-sdk/agent/
|
|
47
|
-
| `@yolk-sdk/agent/
|
|
48
|
-
| `@yolk-sdk/agent/voice
|
|
49
|
-
| `@yolk-sdk/agent/voice/
|
|
20
|
+
| Subpath | Purpose |
|
|
21
|
+
| ------------------------------------------------------ | -------------------------------------------------------------- |
|
|
22
|
+
| `@yolk-sdk/agent/protocol` | Wire messages, events, content, usage, tool schemas |
|
|
23
|
+
| `@yolk-sdk/agent/loop` | Stateless LLM/tool loop |
|
|
24
|
+
| `@yolk-sdk/agent/loop/testing` | Faux provider and tool executor test helpers |
|
|
25
|
+
| `@yolk-sdk/agent/runtime` | Transcript or append-backed runtime orchestration |
|
|
26
|
+
| `@yolk-sdk/agent/client` | HTTP/NDJSON transport, HITL resume, retry/error state helpers |
|
|
27
|
+
| `@yolk-sdk/agent/compaction` | Host-owned compaction budgets, checkpoints, formatting, retry |
|
|
28
|
+
| `@yolk-sdk/agent/tools` | Tool registry, `makeTool`, subagent/question contracts |
|
|
29
|
+
| `@yolk-sdk/agent/react` | Headless React chat hook, reducer, selectors, and render model |
|
|
30
|
+
| `@yolk-sdk/agent/oauth` | Provider-neutral OAuth token and broker contracts |
|
|
31
|
+
| `@yolk-sdk/agent/providers/openai` | OpenAI/Codex OAuth and broker helpers |
|
|
32
|
+
| `@yolk-sdk/agent/providers/openai/codex` | OpenAI Codex request and auth helpers |
|
|
33
|
+
| `@yolk-sdk/agent/providers/openai/codex-usage` | Codex subscription-allowance snapshots |
|
|
34
|
+
| `@yolk-sdk/agent/providers/openai/codex-provider` | Codex LLM provider factory |
|
|
35
|
+
| `@yolk-sdk/agent/providers/openai/provider` | OpenAI-compatible LLM provider factory |
|
|
36
|
+
| `@yolk-sdk/agent/providers/openai/realtime` | OpenAI Realtime session config and event codecs |
|
|
37
|
+
| `@yolk-sdk/agent/providers/openai/speech` | OpenAI text-to-speech and transcription adapters |
|
|
38
|
+
| `@yolk-sdk/agent/providers/vercel/ai-gateway-provider` | Vercel AI Gateway Chat Completions provider factory |
|
|
39
|
+
| `@yolk-sdk/agent/providers/anthropic` | Anthropic/Claude OAuth and broker helpers |
|
|
40
|
+
| `@yolk-sdk/agent/providers/anthropic/claude` | Claude request and auth helpers |
|
|
41
|
+
| `@yolk-sdk/agent/providers/anthropic/usage` | Claude subscription-allowance snapshots |
|
|
42
|
+
| `@yolk-sdk/agent/providers/anthropic/claude-provider` | Claude LLM provider factory |
|
|
43
|
+
| `@yolk-sdk/agent/providers/xai` | Grok subscription OAuth and token broker helpers |
|
|
44
|
+
| `@yolk-sdk/agent/providers/xai/grok` | Grok subscription request and auth helpers |
|
|
45
|
+
| `@yolk-sdk/agent/providers/xai/grok-provider` | Grok subscription LLM provider factory |
|
|
46
|
+
| `@yolk-sdk/agent/providers/subscription-usage` | Shared allowance snapshot and safe error schemas |
|
|
47
|
+
| `@yolk-sdk/agent/skillset` | Portable skill and slash-command parsing/catalogs |
|
|
48
|
+
| `@yolk-sdk/agent/voice` | Voice protocol, controller, tool handler, projection, speech |
|
|
49
|
+
| `@yolk-sdk/agent/voice/browser` | Browser WebRTC voice transport |
|
|
50
|
+
| `@yolk-sdk/agent/voice/react` | Headless browser voice React hook |
|
|
50
51
|
|
|
51
52
|
## Imports
|
|
52
53
|
|
|
@@ -88,7 +89,7 @@ import {
|
|
|
88
89
|
makeAgentChatEventProjectionState,
|
|
89
90
|
useAgentChat
|
|
90
91
|
} from '@yolk-sdk/agent/react'
|
|
91
|
-
import {
|
|
92
|
+
import { makeVercelAiGatewayProviderLayer } from '@yolk-sdk/agent/providers/vercel/ai-gateway-provider'
|
|
92
93
|
```
|
|
93
94
|
|
|
94
95
|
Test helpers live behind their own subpath:
|
|
@@ -153,6 +154,7 @@ limits or apply hidden fallbacks.
|
|
|
153
154
|
| Provider factory | Output-limit field |
|
|
154
155
|
| ---------------------------------- | --------------------- |
|
|
155
156
|
| `makeOpenAiProviderLayer` | `maxCompletionTokens` |
|
|
157
|
+
| `makeVercelAiGatewayProviderLayer` | `maxCompletionTokens` |
|
|
156
158
|
| `makeOpenAiCodexProviderLayer` | none |
|
|
157
159
|
| `makeAnthropicClaudeProviderLayer` | `maxTokens` |
|
|
158
160
|
| `makeXAiGrokProviderLayer` | `maxOutputTokens` |
|
|
@@ -163,6 +165,15 @@ require the matching limit configuration. ChatGPT subscription Codex rejects ven
|
|
|
163
165
|
optional deprecated `maxOutputTokens` compatibility field. `OpenAiProviderLayer` reads both
|
|
164
166
|
`OPENAI_API_KEY` and integer `OPENAI_MAX_COMPLETION_TOKENS` through Effect Config.
|
|
165
167
|
|
|
168
|
+
Vercel AI Gateway uses its OpenAI-compatible JSON Chat Completions endpoint. Pass either an AI
|
|
169
|
+
Gateway API key or Vercel OIDC token as `apiKey`; `maxCompletionTokens` is sent as Gateway
|
|
170
|
+
`max_tokens`. The env-backed `VercelAiGatewayProviderLayer` tries `AI_GATEWAY_API_KEY` first, then
|
|
171
|
+
`VERCEL_OIDC_TOKEN`, and requires integer
|
|
172
|
+
`AI_GATEWAY_MAX_COMPLETION_TOKENS`. Model ids are opaque `provider/model` strings. Hosts may set
|
|
173
|
+
`fallbackModels`, provider `routing`, and optional `http-referer` / `x-title` attribution headers.
|
|
174
|
+
Required authorization and JSON headers cannot be replaced through `extraHeaders`. Only override
|
|
175
|
+
`chatCompletionsUrl` with a trusted proxy because it receives the bearer credential.
|
|
176
|
+
|
|
166
177
|
Grok subscription access uses `https://cli-chat-proxy.grok.com/v1/responses`, not the xAI developer
|
|
167
178
|
API-key endpoint. The adapter sends the required CLI-session and model-routing headers and rejects
|
|
168
179
|
mismatched or expired access-token envelopes before HTTP. The package exports browser-PKCE and
|
|
@@ -202,8 +213,9 @@ policy and emits protocol-visible retry/error state:
|
|
|
202
213
|
- `buildAgentChatItems` can project active retry state as an `AgentChatItem` with `_tag: 'Retry'`.
|
|
203
214
|
- Anthropic prompt-too-long responses become non-retryable `context_overflow`; the host-owned
|
|
204
215
|
compaction wrapper may compact and retry once.
|
|
205
|
-
- Anthropic `max_tokens`
|
|
206
|
-
|
|
216
|
+
- Anthropic `max_tokens` and OpenAI-compatible `finish_reason: "length"` / `"content_filter"`
|
|
217
|
+
completions fail as non-retryable `invalid_response` instead of reporting truncated or filtered
|
|
218
|
+
output as complete.
|
|
207
219
|
|
|
208
220
|
Raw provider response bodies stay out of protocol/UI. Hosts own durable persistence and display of
|
|
209
221
|
typed retry/error state.
|
|
@@ -300,8 +312,9 @@ inside one source fail validation. Hosts own file discovery, storage, enablement
|
|
|
300
312
|
- `AudioPart` with `InlineBase64`, `Url`, or host-owned `Ref` source
|
|
301
313
|
|
|
302
314
|
Build sources with `inlineBase64AttachmentSource`, `urlAttachmentSource`, or
|
|
303
|
-
`refAttachmentSource`. Providers can pass through supported media URLs: OpenAI Chat
|
|
304
|
-
URLs; OpenAI Codex supports image and document URLs; Anthropic supports image
|
|
315
|
+
`refAttachmentSource`. Providers can pass through supported media URLs: OpenAI Chat and Vercel AI
|
|
316
|
+
Gateway support image URLs; OpenAI Codex supports image and document URLs; Anthropic supports image
|
|
317
|
+
and PDF URLs. The Grok
|
|
305
318
|
Responses lowerer can encode image URLs/data URLs, but hosts should enable image capability only for
|
|
306
319
|
subscription models they have verified accept image input. Use
|
|
307
320
|
inline base64 for simple apps, durable URLs for app-owned uploads, or persist `Ref` values and call
|
|
@@ -391,7 +404,7 @@ replay.
|
|
|
391
404
|
|
|
392
405
|
## Parallel tool calls
|
|
393
406
|
|
|
394
|
-
OpenAI, OpenAI Codex, and Grok requests enable vendor parallel tool calls when tools are available. The
|
|
407
|
+
OpenAI, Vercel AI Gateway, OpenAI Codex, and Grok requests enable vendor parallel tool calls when tools are available. The
|
|
395
408
|
Codex stream adapter preserves every sibling function call and suppresses final-response replays
|
|
396
409
|
by call id. The loop executes calls emitted in the same model turn concurrently, bounded by the
|
|
397
410
|
host-configured `LoopConfig.toolConcurrency`; dependent work waits for the next model turn.
|
|
@@ -18,8 +18,8 @@ declare const makeCompactionCheckpointText: (input: CompactionCheckpointInput) =
|
|
|
18
18
|
declare const makeCompactionCheckpointMessage: (input: CompactionCheckpointInput) => UserMessage;
|
|
19
19
|
declare const isCompactionCheckpointText: (value: string) => boolean;
|
|
20
20
|
declare const isCompactionCheckpointMessage: (message: AgentMessage) => boolean;
|
|
21
|
-
declare const dropLeadingCompactionCheckpointMessage: (messages: ReadonlyArray<AgentMessage>) => readonly (
|
|
22
|
-
declare const compactionSummarySourceMessages: (input: CompactionSummarySourceMessageOptions) => readonly (
|
|
21
|
+
declare const dropLeadingCompactionCheckpointMessage: (messages: ReadonlyArray<AgentMessage>) => readonly (import("@yolk-sdk/agent/protocol").ToolResultMessage | UserMessage | import("@yolk-sdk/agent/protocol").AssistantAgentMessage)[];
|
|
22
|
+
declare const compactionSummarySourceMessages: (input: CompactionSummarySourceMessageOptions) => readonly (import("@yolk-sdk/agent/protocol").ToolResultMessage | UserMessage | import("@yolk-sdk/agent/protocol").AssistantAgentMessage)[];
|
|
23
23
|
//#endregion
|
|
24
24
|
export { CompactionCheckpointInput, CompactionSummarySourceMessageOptions, compactionCheckpointCloseTag, compactionCheckpointOpenTag, compactionSummarySourceMessages, defaultCompactionCheckpointHeader, dropLeadingCompactionCheckpointMessage, isCompactionCheckpointMessage, isCompactionCheckpointText, makeCompactionCheckpointMessage, makeCompactionCheckpointText };
|
|
25
25
|
//# sourceMappingURL=checkpoint.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint.d.mts","names":[],"sources":["../../src/compaction/checkpoint.ts"],"mappings":";;;cAEa,2BAAA;AAAA,cACA,4BAAA;AAAA,cACA,iCAAA;AAAA,KAGD,yBAAA;EAAA,SACD,OAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA;EAAA,SACA,WAAA;AAAA;AAAA,KAGC,qCAAA;EAAA,SACD,kBAAA;EAAA,SACA,QAAA,EAAU,aAAa,CAAC,YAAA;AAAA;AAAA,cAGtB,4BAAA,GAAgC,KAAgC,EAAzB,yBAAyB;AAAA,cAahE,+BAAA,GAAmC,KAAA,EAAO,yBAAA,KAAyB,WAI5E;AAAA,cAES,0BAAA,GAA8B,KAAa;AAAA,cAG3C,6BAAA,GAAiC,OAAqB,EAAZ,YAAY;AAAA,cAGtD,sCAAA,GACX,QAAA,EAAU,aAAA,CAAc,YAAA,
|
|
1
|
+
{"version":3,"file":"checkpoint.d.mts","names":[],"sources":["../../src/compaction/checkpoint.ts"],"mappings":";;;cAEa,2BAAA;AAAA,cACA,4BAAA;AAAA,cACA,iCAAA;AAAA,KAGD,yBAAA;EAAA,SACD,OAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA;EAAA,SACA,WAAA;AAAA;AAAA,KAGC,qCAAA;EAAA,SACD,kBAAA;EAAA,SACA,QAAA,EAAU,aAAa,CAAC,YAAA;AAAA;AAAA,cAGtB,4BAAA,GAAgC,KAAgC,EAAzB,yBAAyB;AAAA,cAahE,+BAAA,GAAmC,KAAA,EAAO,yBAAA,KAAyB,WAI5E;AAAA,cAES,0BAAA,GAA8B,KAAa;AAAA,cAG3C,6BAAA,GAAiC,OAAqB,EAAZ,YAAY;AAAA,cAGtD,sCAAA,GACX,QAAA,EAAU,aAAA,CAAc,YAAA,mDAAa,iBAAA,GAAA,WAAA,sCAAA,qBAAA;AAAA,cAO1B,+BAAA,GACX,KAAA,EAAO,qCAAA,kDAAqC,iBAAA,GAAA,WAAA,sCAAA,qBAAA"}
|
|
@@ -3,10 +3,17 @@ import { HttpClient } from "effect/unstable/http";
|
|
|
3
3
|
import { LLMError, LLMProvider, LLMRequest } from "@yolk-sdk/agent/loop";
|
|
4
4
|
|
|
5
5
|
//#region src/providers/openai/provider.d.ts
|
|
6
|
+
type OpenAiProviderIdentity = {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
};
|
|
6
10
|
type OpenAiProviderConfig = {
|
|
7
11
|
readonly chatCompletionsUrl?: string;
|
|
8
|
-
readonly maxCompletionTokens: number;
|
|
9
|
-
readonly
|
|
12
|
+
readonly maxCompletionTokens: number; /** Selects the compatible endpoint's output-limit parameter. Defaults to `max_completion_tokens`. */
|
|
13
|
+
readonly completionTokenField?: 'max_completion_tokens' | 'max_tokens';
|
|
14
|
+
readonly extraHeaders?: Readonly<Record<string, string>>; /** Adds compatible endpoint extensions; canonical model/messages/limit/stream fields win. */
|
|
15
|
+
readonly extraBody?: Readonly<Record<string, unknown>>; /** Customizes safe error metadata for a branded OpenAI-compatible endpoint. */
|
|
16
|
+
readonly providerIdentity?: OpenAiProviderIdentity;
|
|
10
17
|
readonly apiKey: Redacted.Redacted<string>;
|
|
11
18
|
};
|
|
12
19
|
type OpenAiTextContentPart = {
|
|
@@ -54,13 +61,19 @@ type OpenAiTool = {
|
|
|
54
61
|
type OpenAiRequestBody = {
|
|
55
62
|
readonly model: string;
|
|
56
63
|
readonly messages: ReadonlyArray<OpenAiMessage>;
|
|
57
|
-
readonly max_completion_tokens
|
|
64
|
+
readonly max_completion_tokens?: number;
|
|
65
|
+
readonly max_tokens?: number;
|
|
66
|
+
readonly stream: false;
|
|
58
67
|
readonly tools?: ReadonlyArray<OpenAiTool>;
|
|
59
68
|
readonly parallel_tool_calls?: true;
|
|
60
69
|
};
|
|
61
|
-
|
|
70
|
+
type OpenAiRequestBodyConfig = {
|
|
62
71
|
readonly maxCompletionTokens: number;
|
|
63
|
-
|
|
72
|
+
readonly completionTokenField?: 'max_completion_tokens' | 'max_tokens';
|
|
73
|
+
readonly extraBody?: Readonly<Record<string, unknown>>;
|
|
74
|
+
readonly providerName?: string;
|
|
75
|
+
};
|
|
76
|
+
declare const toOpenAiRequestBody: (request: LLMRequest, config: OpenAiRequestBodyConfig) => Effect.Effect<OpenAiRequestBody, LLMError>;
|
|
64
77
|
declare const makeOpenAiProviderLayer: (config: OpenAiProviderConfig) => Layer.Layer<LLMProvider, never, HttpClient.HttpClient>;
|
|
65
78
|
declare const OpenAiProviderLayer: Layer.Layer<LLMProvider, LLMError, never>;
|
|
66
79
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.mts","names":[],"sources":["../../../src/providers/openai/provider.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"provider.d.mts","names":[],"sources":["../../../src/providers/openai/provider.ts"],"mappings":";;;;;KA6CK,sBAAA;EAAA,SACM,EAAA;EAAA,SACA,IAAI;AAAA;AAAA,KAGH,oBAAA;EAAA,SACD,kBAAA;EAAA,SACA,mBAAA,UAFC;EAAA,SAID,oBAAA;EAAA,SACA,YAAA,GAAe,QAAA,CAAS,MAAA,mBAAA;EAAA,SAExB,SAAA,GAAY,QAAA,CAAS,MAAA,oBAAA;EAAA,SAErB,gBAAA,GAAmB,sBAAA;EAAA,SACnB,MAAA,EAAQ,QAAA,CAAS,QAAA;AAAA;AAAA,KAGvB,qBAAA;EAAA,SACM,IAAA;EAAA,SACA,IAAI;AAAA;AAAA,KAGV,sBAAA;EAAA,SACM,IAAA;EAAA,SACA,SAAA;IAAA,SACE,GAAA;EAAA;AAAA;AAAA,KAIR,iBAAA,YAA6B,aAAA,CAAc,qBAAA,GAAwB,sBAAA;AAAA,KAEnE,cAAA;EAAA,SACM,EAAA;EAAA,SACA,IAAA;EAAA,SACA,QAAA;IAAA,SACE,IAAA;IAAA,SACA,SAAA;EAAA;AAAA;AAAA,KAIR,aAAA;EAAA,SACU,IAAA;EAAA,SAAyB,OAAA;AAAA;EAAA,SACzB,IAAA;EAAA,SAAuB,OAAA,EAAS,iBAAA;AAAA;EAAA,SAEhC,IAAA;EAAA,SACA,OAAA;EAAA,SACA,UAAA,GAAa,aAAA,CAAc,cAAA;AAAA;EAAA,SAE3B,IAAA;EAAA,SAAuB,YAAA;EAAA,SAA+B,OAAA;AAAA;AAAA,KAEhE,UAAA;EAAA,SACM,IAAA;EAAA,SACA,QAAA;IAAA,SACE,IAAA;IAAA,SACA,WAAA;IAAA,SACA,UAAA;EAAA;AAAA;AAAA,KAIR,iBAAA;EAAA,SACM,KAAA;EAAA,SACA,QAAA,EAAU,aAAA,CAAc,aAAA;EAAA,SACxB,qBAAA;EAAA,SACA,UAAA;EAAA,SACA,MAAA;EAAA,SACA,KAAA,GAAQ,aAAA,CAAc,UAAA;EAAA,SACtB,mBAAA;AAAA;AAAA,KAGN,uBAAA;EAAA,SACM,mBAAA;EAAA,SACA,oBAAA;EAAA,SACA,SAAA,GAAY,QAAQ,CAAC,MAAA;EAAA,SACrB,YAAA;AAAA;AAAA,cA8QE,mBAAA,GACX,OAAA,EAAS,UAAA,EACT,MAAA,EAAQ,uBAAA,KACP,MAAA,CAAO,MAAA,CAAO,iBAAA,EAAmB,QAAA;AAAA,cAwOvB,uBAAA,GAA2B,MAAA,EAAQ,oBAAA,KAAoB,KAAA,CAAA,KAAA,CAAA,WAAA,SAAA,UAAA,CAAA,UAAA;AAAA,cAevD,mBAAA,EAAmB,KAAA,CAAA,KAAA,CAAA,WAAA,EAAA,QAAA"}
|
|
@@ -6,6 +6,10 @@ import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/
|
|
|
6
6
|
import * as Schema from "effect/Schema";
|
|
7
7
|
import { LLMDone, LLMError, LLMProvider, LLMTextDelta as LLMTextDelta$1, LLMToolCall, LLMUsage } from "@yolk-sdk/agent/loop";
|
|
8
8
|
//#region src/providers/openai/provider.ts
|
|
9
|
+
const defaultOpenAiProviderIdentity = {
|
|
10
|
+
id: "openai",
|
|
11
|
+
name: "OpenAI"
|
|
12
|
+
};
|
|
9
13
|
var OpenAiFunctionResponse = class extends Schema.Class("OpenAiFunctionResponse")({
|
|
10
14
|
name: Schema.String,
|
|
11
15
|
arguments: Schema.String
|
|
@@ -19,7 +23,10 @@ var OpenAiMessageResponse = class extends Schema.Class("OpenAiMessageResponse")(
|
|
|
19
23
|
content: Schema.NullOr(Schema.String),
|
|
20
24
|
tool_calls: Schema.optional(Schema.Array(OpenAiToolCallResponse))
|
|
21
25
|
}) {};
|
|
22
|
-
var OpenAiChoiceResponse = class extends Schema.Class("OpenAiChoiceResponse")({
|
|
26
|
+
var OpenAiChoiceResponse = class extends Schema.Class("OpenAiChoiceResponse")({
|
|
27
|
+
message: OpenAiMessageResponse,
|
|
28
|
+
finish_reason: Schema.optional(Schema.NullOr(Schema.String))
|
|
29
|
+
}) {};
|
|
23
30
|
var OpenAiPromptTokensDetails = class extends Schema.Class("OpenAiPromptTokensDetails")({ cached_tokens: Schema.optional(Schema.Number) }) {};
|
|
24
31
|
var OpenAiCompletionTokensDetails = class extends Schema.Class("OpenAiCompletionTokensDetails")({ reasoning_tokens: Schema.optional(Schema.Number) }) {};
|
|
25
32
|
var OpenAiUsageResponse = class extends Schema.Class("OpenAiUsageResponse")({
|
|
@@ -54,13 +61,13 @@ const decodeJsonString = (raw, message) => Schema.decodeUnknownEffect(Schema.Unk
|
|
|
54
61
|
message: `${message}: ${unknownToMessage(error)}`,
|
|
55
62
|
retryable: false
|
|
56
63
|
})));
|
|
57
|
-
const unsupportedContentError = (contentType) => new LLMError({
|
|
64
|
+
const unsupportedContentError = (contentType, providerName) => new LLMError({
|
|
58
65
|
cause: "provider_error",
|
|
59
|
-
message: `${contentType} content is not supported by the
|
|
66
|
+
message: `${contentType} content is not supported by the ${providerName} provider yet`,
|
|
60
67
|
retryable: false
|
|
61
68
|
});
|
|
62
|
-
const textDocumentToOpenAiPart = (part) => attachmentSourceText(part.source).pipe(Effect.mapError(() => unsupportedContentError("Invalid document text")), Effect.flatMap(Option.match({
|
|
63
|
-
onNone: () => Effect.fail(unsupportedContentError("Unresolved document source")),
|
|
69
|
+
const textDocumentToOpenAiPart = (part, providerName) => attachmentSourceText(part.source).pipe(Effect.mapError(() => unsupportedContentError("Invalid document text", providerName)), Effect.flatMap(Option.match({
|
|
70
|
+
onNone: () => Effect.fail(unsupportedContentError("Unresolved document source", providerName)),
|
|
64
71
|
onSome: (text) => {
|
|
65
72
|
const block = {
|
|
66
73
|
type: "text",
|
|
@@ -69,67 +76,67 @@ const textDocumentToOpenAiPart = (part) => attachmentSourceText(part.source).pip
|
|
|
69
76
|
return Effect.succeed(block);
|
|
70
77
|
}
|
|
71
78
|
})));
|
|
72
|
-
const contentPartToUserPart = (part) => {
|
|
79
|
+
const contentPartToUserPart = (part, providerName) => {
|
|
73
80
|
switch (part._tag) {
|
|
74
81
|
case "Text": return Effect.succeed({
|
|
75
82
|
type: "text",
|
|
76
83
|
text: part.text
|
|
77
84
|
});
|
|
78
85
|
case "Image": return Option.match(attachmentSourceUrl(part.source, part.mimeType), {
|
|
79
|
-
onNone: () => Effect.fail(unsupportedContentError("Unresolved image source")),
|
|
86
|
+
onNone: () => Effect.fail(unsupportedContentError("Unresolved image source", providerName)),
|
|
80
87
|
onSome: (url) => Effect.succeed({
|
|
81
88
|
type: "image_url",
|
|
82
89
|
image_url: { url }
|
|
83
90
|
})
|
|
84
91
|
});
|
|
85
|
-
case "Document": return isTextDocumentMimeType(part.mimeType) ? textDocumentToOpenAiPart(part) : Effect.fail(unsupportedContentError("Document"));
|
|
86
|
-
case "Audio": return Effect.fail(unsupportedContentError("Audio"));
|
|
92
|
+
case "Document": return isTextDocumentMimeType(part.mimeType) ? textDocumentToOpenAiPart(part, providerName) : Effect.fail(unsupportedContentError("Document", providerName));
|
|
93
|
+
case "Audio": return Effect.fail(unsupportedContentError("Audio", providerName));
|
|
87
94
|
}
|
|
88
95
|
};
|
|
89
|
-
const contentToUserContent = (content) => typeof content === "string" ? Effect.succeed(content) : Effect.forEach(content, contentPartToUserPart);
|
|
90
|
-
const contentPartToText = (part, owner) => {
|
|
96
|
+
const contentToUserContent = (content, providerName) => typeof content === "string" ? Effect.succeed(content) : Effect.forEach(content, (part) => contentPartToUserPart(part, providerName));
|
|
97
|
+
const contentPartToText = (part, owner, providerName) => {
|
|
91
98
|
switch (part._tag) {
|
|
92
99
|
case "Text": return Effect.succeed(part.text);
|
|
93
|
-
case "Image": return Effect.fail(unsupportedContentError(`${owner} image
|
|
94
|
-
case "Document": return Effect.fail(unsupportedContentError(`${owner} document
|
|
95
|
-
case "Audio": return Effect.fail(unsupportedContentError(`${owner} audio
|
|
100
|
+
case "Image": return Effect.fail(unsupportedContentError(`${owner} image`, providerName));
|
|
101
|
+
case "Document": return Effect.fail(unsupportedContentError(`${owner} document`, providerName));
|
|
102
|
+
case "Audio": return Effect.fail(unsupportedContentError(`${owner} audio`, providerName));
|
|
96
103
|
}
|
|
97
104
|
};
|
|
98
|
-
const contentToText = (content, owner) => typeof content === "string" ? Effect.succeed(content) : Effect.forEach(content, (part) => contentPartToText(part, owner)).pipe(Effect.map((textParts) => textParts.join("\n")));
|
|
99
|
-
const serializeToolArguments = (params) => encodeJsonString(params,
|
|
100
|
-
const toolCallToOpenAiToolCall = (call) => Effect.gen(function* () {
|
|
105
|
+
const contentToText = (content, owner, providerName) => typeof content === "string" ? Effect.succeed(content) : Effect.forEach(content, (part) => contentPartToText(part, owner, providerName)).pipe(Effect.map((textParts) => textParts.join("\n")));
|
|
106
|
+
const serializeToolArguments = (params, providerName) => encodeJsonString(params, `Could not serialize ${providerName} tool arguments`);
|
|
107
|
+
const toolCallToOpenAiToolCall = (call, providerName) => Effect.gen(function* () {
|
|
101
108
|
return {
|
|
102
109
|
id: call.id,
|
|
103
110
|
type: "function",
|
|
104
111
|
function: {
|
|
105
112
|
name: call.name,
|
|
106
|
-
arguments: yield* serializeToolArguments(call.params)
|
|
113
|
+
arguments: yield* serializeToolArguments(call.params, providerName)
|
|
107
114
|
}
|
|
108
115
|
};
|
|
109
116
|
});
|
|
110
|
-
const toOpenAiMessage = (message) => Effect.gen(function* () {
|
|
117
|
+
const toOpenAiMessage = (message, providerName) => Effect.gen(function* () {
|
|
111
118
|
switch (message._tag) {
|
|
112
119
|
case "User": return {
|
|
113
120
|
role: "user",
|
|
114
|
-
content: yield* contentToUserContent(prependMessageContextToContent(message.content, messageContextText(message)))
|
|
121
|
+
content: yield* contentToUserContent(prependMessageContextToContent(message.content, messageContextText(message)), providerName)
|
|
115
122
|
};
|
|
116
123
|
case "Assistant": {
|
|
117
124
|
const content = prependMessageContextToContent(assistantContent(message), messageContextText(message));
|
|
118
|
-
const toolCalls = yield* Effect.forEach(assistantHostToolCalls(message), toolCallToOpenAiToolCall);
|
|
125
|
+
const toolCalls = yield* Effect.forEach(assistantHostToolCalls(message), (call) => toolCallToOpenAiToolCall(call, providerName));
|
|
119
126
|
if (toolCalls.length > 0) return {
|
|
120
127
|
role: "assistant",
|
|
121
|
-
content: yield* contentToText(content, "Assistant"),
|
|
128
|
+
content: yield* contentToText(content, "Assistant", providerName),
|
|
122
129
|
tool_calls: toolCalls
|
|
123
130
|
};
|
|
124
131
|
return {
|
|
125
132
|
role: "assistant",
|
|
126
|
-
content: yield* contentToText(content, "Assistant")
|
|
133
|
+
content: yield* contentToText(content, "Assistant", providerName)
|
|
127
134
|
};
|
|
128
135
|
}
|
|
129
136
|
case "ToolResult": return {
|
|
130
137
|
role: "tool",
|
|
131
138
|
tool_call_id: message.toolCallId,
|
|
132
|
-
content: yield* contentToText(prependMessageContextToContent(message.content, messageContextText(message)), "Tool result")
|
|
139
|
+
content: yield* contentToText(prependMessageContextToContent(message.content, messageContextText(message)), "Tool result", providerName)
|
|
133
140
|
};
|
|
134
141
|
}
|
|
135
142
|
});
|
|
@@ -142,15 +149,24 @@ const toOpenAiTool = (tool) => ({
|
|
|
142
149
|
}
|
|
143
150
|
});
|
|
144
151
|
const toOpenAiRequestBody = (request, config) => Effect.gen(function* () {
|
|
152
|
+
const providerName = config.providerName ?? defaultOpenAiProviderIdentity.name;
|
|
153
|
+
if (!Number.isSafeInteger(config.maxCompletionTokens) || config.maxCompletionTokens <= 0) return yield* Effect.fail(new LLMError({
|
|
154
|
+
cause: "validation_error",
|
|
155
|
+
message: `${providerName} maxCompletionTokens must be a positive safe integer`,
|
|
156
|
+
retryable: false
|
|
157
|
+
}));
|
|
145
158
|
yield* validateProviderTranscript(request.messages);
|
|
146
159
|
const messages = [{
|
|
147
160
|
role: "system",
|
|
148
161
|
content: request.systemPrompt
|
|
149
|
-
}, ...yield* Effect.forEach(request.messages, toOpenAiMessage)];
|
|
162
|
+
}, ...yield* Effect.forEach(request.messages, (message) => toOpenAiMessage(message, providerName))];
|
|
163
|
+
const completionTokenLimit = config.completionTokenField === "max_tokens" ? { max_tokens: config.maxCompletionTokens } : { max_completion_tokens: config.maxCompletionTokens };
|
|
150
164
|
const body = {
|
|
165
|
+
...config.extraBody,
|
|
151
166
|
model: request.model,
|
|
152
167
|
messages,
|
|
153
|
-
|
|
168
|
+
...completionTokenLimit,
|
|
169
|
+
stream: false
|
|
154
170
|
};
|
|
155
171
|
if (request.tools.length === 0) return body;
|
|
156
172
|
return {
|
|
@@ -159,11 +175,21 @@ const toOpenAiRequestBody = (request, config) => Effect.gen(function* () {
|
|
|
159
175
|
parallel_tool_calls: true
|
|
160
176
|
};
|
|
161
177
|
});
|
|
162
|
-
const parseToolArguments = (raw) => decodeJsonString(raw,
|
|
163
|
-
const toLlmEvents = (
|
|
164
|
-
|
|
178
|
+
const parseToolArguments = (raw, providerName) => decodeJsonString(raw, `Invalid ${providerName} tool arguments JSON`);
|
|
179
|
+
const toLlmEvents = (choice, providerIdentity) => Effect.gen(function* () {
|
|
180
|
+
if (choice.finish_reason === "length" || choice.finish_reason === "content_filter") return yield* Effect.fail(new LLMError({
|
|
181
|
+
cause: "invalid_response",
|
|
182
|
+
message: `${providerIdentity.name} response stopped with ${choice.finish_reason}`,
|
|
183
|
+
retryable: false,
|
|
184
|
+
provider: providerErrorInfo({
|
|
185
|
+
provider: providerIdentity.id,
|
|
186
|
+
kind: "invalid_response",
|
|
187
|
+
providerCode: choice.finish_reason
|
|
188
|
+
})
|
|
189
|
+
}));
|
|
190
|
+
const content = choice.message.content ?? "";
|
|
165
191
|
const textEvents = content.length > 0 ? [LLMTextDelta$1.make({ text: content })] : [];
|
|
166
|
-
const toolCallEvents = yield* Effect.forEach(message.tool_calls ?? [], (call) => parseToolArguments(call.function.arguments).pipe(Effect.map((params) => LLMToolCall.make({ call: ToolCall.make({
|
|
192
|
+
const toolCallEvents = yield* Effect.forEach(choice.message.tool_calls ?? [], (call) => parseToolArguments(call.function.arguments, providerIdentity.name).pipe(Effect.map((params) => LLMToolCall.make({ call: ToolCall.make({
|
|
167
193
|
id: call.id,
|
|
168
194
|
name: call.function.name,
|
|
169
195
|
params
|
|
@@ -187,64 +213,70 @@ const toAgentUsage = (usage) => AgentUsage.make({
|
|
|
187
213
|
text: usage.completion_tokens - (usage.completion_tokens_details?.reasoning_tokens ?? 0)
|
|
188
214
|
})
|
|
189
215
|
});
|
|
190
|
-
const toHttpClientLlmError = (
|
|
216
|
+
const toHttpClientLlmError = (providerIdentity, retryable) => (error) => new LLMError({
|
|
191
217
|
cause: "provider_error",
|
|
192
|
-
message: `${
|
|
218
|
+
message: `${providerIdentity.name} request failed: ${error.message}`,
|
|
193
219
|
retryable,
|
|
194
220
|
provider: providerErrorInfo({
|
|
195
|
-
provider:
|
|
221
|
+
provider: providerIdentity.id,
|
|
196
222
|
kind: retryable ? "network" : "unknown"
|
|
197
223
|
})
|
|
198
224
|
});
|
|
199
|
-
const parseOpenAiResponseJson = (response) => response.json.pipe(Effect.mapError((error) => new LLMError({
|
|
225
|
+
const parseOpenAiResponseJson = (response, providerName) => response.json.pipe(Effect.mapError((error) => new LLMError({
|
|
200
226
|
cause: "invalid_response",
|
|
201
|
-
message: `Could not parse
|
|
227
|
+
message: `Could not parse ${providerName} response JSON: ${error.message}`,
|
|
202
228
|
retryable: false
|
|
203
229
|
})));
|
|
204
230
|
const sendOpenAiRequest = (config, request, client) => Effect.gen(function* () {
|
|
205
|
-
const
|
|
231
|
+
const providerIdentity = config.providerIdentity ?? defaultOpenAiProviderIdentity;
|
|
232
|
+
const serializedBody = yield* encodeJsonString(replaceLoneSurrogatesDeep(yield* toOpenAiRequestBody(request, {
|
|
233
|
+
maxCompletionTokens: config.maxCompletionTokens,
|
|
234
|
+
providerName: providerIdentity.name,
|
|
235
|
+
...config.completionTokenField === void 0 ? {} : { completionTokenField: config.completionTokenField },
|
|
236
|
+
...config.extraBody === void 0 ? {} : { extraBody: config.extraBody }
|
|
237
|
+
})), `Could not serialize ${providerIdentity.name} request`);
|
|
206
238
|
const httpRequest = HttpClientRequest.post(config.chatCompletionsUrl ?? "https://api.openai.com/v1/chat/completions").pipe(HttpClientRequest.setHeaders({
|
|
239
|
+
...config.extraHeaders,
|
|
207
240
|
accept: "application/json",
|
|
208
241
|
authorization: `Bearer ${Redacted.value(config.apiKey)}`,
|
|
209
|
-
"content-type": "application/json"
|
|
210
|
-
...config.extraHeaders
|
|
242
|
+
"content-type": "application/json"
|
|
211
243
|
}), HttpClientRequest.bodyText(serializedBody, "application/json"));
|
|
212
|
-
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(toHttpClientLlmError(
|
|
244
|
+
const response = yield* client.execute(httpRequest).pipe(Effect.mapError(toHttpClientLlmError(providerIdentity, true)));
|
|
213
245
|
if (response.status < 200 || response.status >= 300) {
|
|
214
246
|
const errorText = yield* response.text.pipe(Effect.mapError((error) => new LLMError({
|
|
215
247
|
cause: "provider_error",
|
|
216
|
-
message: `Could not read
|
|
248
|
+
message: `Could not read ${providerIdentity.name} error body: ${error.message}`,
|
|
217
249
|
retryable: false
|
|
218
250
|
})));
|
|
219
251
|
const provider = classifyProviderFailure({
|
|
220
|
-
provider:
|
|
252
|
+
provider: providerIdentity.id,
|
|
221
253
|
status: response.status,
|
|
222
254
|
headers: response.headers,
|
|
223
255
|
body: errorText
|
|
224
256
|
});
|
|
225
257
|
return yield* Effect.fail(new LLMError({
|
|
226
258
|
cause: providerFailureCause(provider.kind),
|
|
227
|
-
message:
|
|
259
|
+
message: `${providerIdentity.name} returned ${response.status}`,
|
|
228
260
|
retryable: providerFailureRetryable(provider.kind),
|
|
229
261
|
provider
|
|
230
262
|
}));
|
|
231
263
|
}
|
|
232
|
-
const json = yield* parseOpenAiResponseJson(response);
|
|
264
|
+
const json = yield* parseOpenAiResponseJson(response, providerIdentity.name);
|
|
233
265
|
const parsed = yield* Schema.decodeUnknownEffect(OpenAiChatCompletionResponse)(json).pipe(Effect.mapError((error) => new LLMError({
|
|
234
266
|
cause: "invalid_response",
|
|
235
|
-
message: `Invalid
|
|
267
|
+
message: `Invalid ${providerIdentity.name} response: ${unknownToMessage(error)}`,
|
|
236
268
|
retryable: false
|
|
237
269
|
})));
|
|
238
270
|
const choice = parsed.choices[0];
|
|
239
271
|
if (choice === void 0) return yield* Effect.fail(new LLMError({
|
|
240
272
|
cause: "invalid_response",
|
|
241
|
-
message:
|
|
273
|
+
message: `${providerIdentity.name} response contained no choices`,
|
|
242
274
|
retryable: false
|
|
243
275
|
}));
|
|
244
|
-
const events = yield* toLlmEvents(choice
|
|
276
|
+
const events = yield* toLlmEvents(choice, providerIdentity);
|
|
245
277
|
if (parsed.usage === void 0) return events;
|
|
246
278
|
return [...events, LLMUsage.make({ usage: toAgentUsage(parsed.usage) })];
|
|
247
|
-
}).pipe(Effect.withSpan(
|
|
279
|
+
}).pipe(Effect.withSpan(`${config.providerIdentity?.id ?? defaultOpenAiProviderIdentity.id}.stream`));
|
|
248
280
|
const makeOpenAiProviderLayer = (config) => Layer.effect(LLMProvider, Effect.gen(function* () {
|
|
249
281
|
const client = yield* HttpClient.HttpClient;
|
|
250
282
|
return LLMProvider.of({ stream: (request) => Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(Stream.flatMap((events) => Stream.fromIterable(events))) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.mjs","names":["LLMTextDelta"],"sources":["../../../src/providers/openai/provider.ts"],"sourcesContent":["import { Config, Context, Effect, Layer, Option, Redacted, Stream } from 'effect'\nimport {\n FetchHttpClient,\n HttpClient,\n HttpClientRequest,\n type HttpClientError,\n type HttpClientResponse\n} from 'effect/unstable/http'\nimport * as Schema from 'effect/Schema'\nimport {\n ToolCall,\n AgentInputUsage,\n AgentOutputUsage,\n AgentUsage,\n attachmentSourceText,\n attachmentSourceUrl,\n assistantContent,\n assistantHostToolCalls,\n isTextDocumentMimeType,\n messageContextText,\n replaceLoneSurrogatesDeep,\n prependMessageContextToContent,\n type AgentMessage,\n type Content,\n type ContentPart,\n type ToolDef\n} from '@yolk-sdk/agent/protocol'\nimport {\n LLMError,\n LLMDone,\n LLMProvider,\n LLMTextDelta,\n LLMToolCall,\n LLMUsage,\n type LLMEvent,\n type LLMRequest\n} from '@yolk-sdk/agent/loop'\nimport {\n classifyProviderFailure,\n providerErrorInfo,\n providerFailureCause,\n providerFailureRetryable\n} from '../provider-error.ts'\nimport { validateProviderTranscript } from '../transcript.ts'\n\nexport type OpenAiProviderConfig = {\n readonly chatCompletionsUrl?: string\n readonly maxCompletionTokens: number\n readonly extraHeaders?: Readonly<Record<string, string>>\n readonly apiKey: Redacted.Redacted<string>\n}\n\ntype OpenAiTextContentPart = {\n readonly type: 'text'\n readonly text: string\n}\n\ntype OpenAiImageContentPart = {\n readonly type: 'image_url'\n readonly image_url: {\n readonly url: string\n }\n}\n\ntype OpenAiUserContent = string | ReadonlyArray<OpenAiTextContentPart | OpenAiImageContentPart>\n\ntype OpenAiToolCall = {\n readonly id: string\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly arguments: string\n }\n}\n\ntype OpenAiMessage =\n | { readonly role: 'system'; readonly content: string }\n | { readonly role: 'user'; readonly content: OpenAiUserContent }\n | {\n readonly role: 'assistant'\n readonly content: string | null\n readonly tool_calls?: ReadonlyArray<OpenAiToolCall>\n }\n | { readonly role: 'tool'; readonly tool_call_id: string; readonly content: string }\n\ntype OpenAiTool = {\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly description: string\n readonly parameters: unknown\n }\n}\n\ntype OpenAiRequestBody = {\n readonly model: string\n readonly messages: ReadonlyArray<OpenAiMessage>\n readonly max_completion_tokens: number\n readonly tools?: ReadonlyArray<OpenAiTool>\n readonly parallel_tool_calls?: true\n}\n\nclass OpenAiFunctionResponse extends Schema.Class<OpenAiFunctionResponse>('OpenAiFunctionResponse')(\n {\n name: Schema.String,\n arguments: Schema.String\n }\n) {}\n\nclass OpenAiToolCallResponse extends Schema.Class<OpenAiToolCallResponse>('OpenAiToolCallResponse')(\n {\n id: Schema.String,\n type: Schema.Literals(['function']),\n function: OpenAiFunctionResponse\n }\n) {}\n\nclass OpenAiMessageResponse extends Schema.Class<OpenAiMessageResponse>('OpenAiMessageResponse')({\n content: Schema.NullOr(Schema.String),\n tool_calls: Schema.optional(Schema.Array(OpenAiToolCallResponse))\n}) {}\n\nclass OpenAiChoiceResponse extends Schema.Class<OpenAiChoiceResponse>('OpenAiChoiceResponse')({\n message: OpenAiMessageResponse\n}) {}\n\nclass OpenAiPromptTokensDetails extends Schema.Class<OpenAiPromptTokensDetails>(\n 'OpenAiPromptTokensDetails'\n)({\n cached_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiCompletionTokensDetails extends Schema.Class<OpenAiCompletionTokensDetails>(\n 'OpenAiCompletionTokensDetails'\n)({\n reasoning_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiUsageResponse extends Schema.Class<OpenAiUsageResponse>('OpenAiUsageResponse')({\n prompt_tokens: Schema.Number,\n completion_tokens: Schema.Number,\n prompt_tokens_details: Schema.optional(OpenAiPromptTokensDetails),\n completion_tokens_details: Schema.optional(OpenAiCompletionTokensDetails)\n}) {}\n\nclass OpenAiChatCompletionResponse extends Schema.Class<OpenAiChatCompletionResponse>(\n 'OpenAiChatCompletionResponse'\n)({\n choices: Schema.Array(OpenAiChoiceResponse),\n usage: Schema.optional(OpenAiUsageResponse)\n}) {}\n\nclass OpenAiConfig extends Context.Service<OpenAiConfig, OpenAiProviderConfig>()(\n '@app/OpenAiConfig'\n) {}\n\nconst OpenAiConfigLayer = Layer.effect(\n OpenAiConfig,\n Effect.gen(function* () {\n const apiKey = yield* Config.redacted('OPENAI_API_KEY')\n const maxCompletionTokens = yield* Config.int('OPENAI_MAX_COMPLETION_TOKENS')\n return { apiKey, maxCompletionTokens }\n }).pipe(\n Effect.mapError(\n () =>\n new LLMError({\n cause: 'provider_error',\n message: 'OpenAI provider environment configuration missing',\n retryable: false\n })\n )\n )\n)\n\nconst unknownToMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\nconst encodeJsonString = (value: unknown, message: string) =>\n Schema.encodeUnknownEffect(Schema.UnknownFromJsonString)(value).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst decodeJsonString = (raw: string, message: string) =>\n Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)(raw).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst unsupportedContentError = (contentType: string) =>\n new LLMError({\n cause: 'provider_error',\n message: `${contentType} content is not supported by the OpenAI provider yet`,\n retryable: false\n })\n\nconst textDocumentToOpenAiPart = (part: Extract<ContentPart, { readonly _tag: 'Document' }>) =>\n attachmentSourceText(part.source).pipe(\n Effect.mapError(() => unsupportedContentError('Invalid document text')),\n Effect.flatMap(\n Option.match({\n onNone: () => Effect.fail(unsupportedContentError('Unresolved document source')),\n onSome: text => {\n const block: OpenAiTextContentPart = {\n type: 'text',\n text: `Document: ${part.title ?? part.filename}\\n\\n${text}`\n }\n\n return Effect.succeed(block)\n }\n })\n )\n )\n\nconst contentPartToUserPart = (\n part: ContentPart\n): Effect.Effect<OpenAiTextContentPart | OpenAiImageContentPart, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed({ type: 'text', text: part.text })\n case 'Image':\n return Option.match(attachmentSourceUrl(part.source, part.mimeType), {\n onNone: () => Effect.fail(unsupportedContentError('Unresolved image source')),\n onSome: url => Effect.succeed({ type: 'image_url', image_url: { url } })\n })\n case 'Document':\n return isTextDocumentMimeType(part.mimeType)\n ? textDocumentToOpenAiPart(part)\n : Effect.fail(unsupportedContentError('Document'))\n case 'Audio':\n return Effect.fail(unsupportedContentError('Audio'))\n }\n}\n\nconst contentToUserContent = (content: Content): Effect.Effect<OpenAiUserContent, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, contentPartToUserPart)\n\nconst contentPartToText = (part: ContentPart, owner: string): Effect.Effect<string, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed(part.text)\n case 'Image':\n return Effect.fail(unsupportedContentError(`${owner} image`))\n case 'Document':\n return Effect.fail(unsupportedContentError(`${owner} document`))\n case 'Audio':\n return Effect.fail(unsupportedContentError(`${owner} audio`))\n }\n}\n\nconst contentToText = (content: Content, owner: string): Effect.Effect<string, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, part => contentPartToText(part, owner)).pipe(\n Effect.map(textParts => textParts.join('\\n'))\n )\n\nconst serializeToolArguments = (params: unknown) =>\n encodeJsonString(params, 'Could not serialize OpenAI tool arguments')\n\nconst toolCallToOpenAiToolCall = (call: ToolCall): Effect.Effect<OpenAiToolCall, LLMError> =>\n Effect.gen(function* () {\n return {\n id: call.id,\n type: 'function',\n function: {\n name: call.name,\n arguments: yield* serializeToolArguments(call.params)\n }\n }\n })\n\nconst toOpenAiMessage = (message: AgentMessage): Effect.Effect<OpenAiMessage, LLMError> =>\n Effect.gen(function* () {\n switch (message._tag) {\n case 'User':\n return {\n role: 'user',\n content: yield* contentToUserContent(\n prependMessageContextToContent(message.content, messageContextText(message))\n )\n }\n case 'Assistant': {\n const content = prependMessageContextToContent(\n assistantContent(message),\n messageContextText(message)\n )\n const toolCalls = yield* Effect.forEach(\n assistantHostToolCalls(message),\n toolCallToOpenAiToolCall\n )\n\n if (toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant'),\n tool_calls: toolCalls\n }\n }\n\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant')\n }\n }\n case 'ToolResult':\n return {\n role: 'tool',\n tool_call_id: message.toolCallId,\n content: yield* contentToText(\n prependMessageContextToContent(message.content, messageContextText(message)),\n 'Tool result'\n )\n }\n }\n })\n\nconst toOpenAiTool = (tool: ToolDef): OpenAiTool => ({\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters\n }\n})\n\nexport const toOpenAiRequestBody = (\n request: LLMRequest,\n config: { readonly maxCompletionTokens: number }\n): Effect.Effect<OpenAiRequestBody, LLMError> =>\n Effect.gen(function* () {\n yield* validateProviderTranscript(request.messages)\n const systemMessage: OpenAiMessage = { role: 'system', content: request.systemPrompt }\n const requestMessages = yield* Effect.forEach(request.messages, toOpenAiMessage)\n const messages = [systemMessage, ...requestMessages]\n\n const body = {\n model: request.model,\n messages,\n max_completion_tokens: config.maxCompletionTokens\n }\n\n if (request.tools.length === 0) {\n return body\n }\n\n return {\n ...body,\n tools: request.tools.map(toOpenAiTool),\n parallel_tool_calls: true\n }\n })\n\nconst parseToolArguments = (raw: string) =>\n decodeJsonString(raw, 'Invalid OpenAI tool arguments JSON')\n\nconst toLlmEvents = (\n message: OpenAiMessageResponse\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n const content = message.content ?? ''\n const textEvents = content.length > 0 ? [LLMTextDelta.make({ text: content })] : []\n const toolCallEvents = yield* Effect.forEach(message.tool_calls ?? [], call =>\n parseToolArguments(call.function.arguments).pipe(\n Effect.map(params =>\n LLMToolCall.make({\n call: ToolCall.make({\n id: call.id,\n name: call.function.name,\n params\n })\n })\n )\n )\n )\n\n if (toolCallEvents.length > 0) {\n return [...textEvents, ...toolCallEvents, LLMDone.make({ stopReason: 'tool_use' })]\n }\n\n return [...textEvents, LLMDone.make({ stopReason: 'stop' })]\n })\n\nconst toAgentUsage = (usage: OpenAiUsageResponse) =>\n AgentUsage.make({\n input: AgentInputUsage.make({\n total: usage.prompt_tokens,\n uncached: usage.prompt_tokens - (usage.prompt_tokens_details?.cached_tokens ?? 0),\n cacheRead: usage.prompt_tokens_details?.cached_tokens\n }),\n output: AgentOutputUsage.make({\n total: usage.completion_tokens,\n reasoning: usage.completion_tokens_details?.reasoning_tokens,\n text: usage.completion_tokens - (usage.completion_tokens_details?.reasoning_tokens ?? 0)\n })\n })\n\nconst toHttpClientLlmError =\n (message: string, retryable: boolean) => (error: HttpClientError.HttpClientError) =>\n new LLMError({\n cause: 'provider_error',\n message: `${message}: ${error.message}`,\n retryable,\n provider: providerErrorInfo({\n provider: 'openai',\n kind: retryable ? 'network' : 'unknown'\n })\n })\n\nconst parseOpenAiResponseJson = (\n response: HttpClientResponse.HttpClientResponse\n): Effect.Effect<unknown, LLMError> =>\n response.json.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Could not parse OpenAI response JSON: ${error.message}`,\n retryable: false\n })\n )\n )\n\nconst sendOpenAiRequest = (\n config: OpenAiProviderConfig,\n request: LLMRequest,\n client: HttpClient.HttpClient\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n const body = yield* toOpenAiRequestBody(request, config)\n // Replayed transcripts can carry lone surrogates; harden the lowered\n // body so one bad historical string cannot poison every model call.\n const serializedBody = yield* encodeJsonString(\n replaceLoneSurrogatesDeep(body),\n 'Could not serialize OpenAI request'\n )\n\n const httpRequest = HttpClientRequest.post(\n config.chatCompletionsUrl ?? 'https://api.openai.com/v1/chat/completions'\n ).pipe(\n HttpClientRequest.setHeaders({\n accept: 'application/json',\n authorization: `Bearer ${Redacted.value(config.apiKey)}`,\n 'content-type': 'application/json',\n ...config.extraHeaders\n }),\n HttpClientRequest.bodyText(serializedBody, 'application/json')\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError(toHttpClientLlmError('OpenAI request failed', true)))\n\n if (response.status < 200 || response.status >= 300) {\n const errorText = yield* response.text.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `Could not read OpenAI error body: ${error.message}`,\n retryable: false\n })\n )\n )\n\n const provider = classifyProviderFailure({\n provider: 'openai',\n status: response.status,\n headers: response.headers,\n body: errorText\n })\n\n return yield* Effect.fail(\n new LLMError({\n cause: providerFailureCause(provider.kind),\n message: `OpenAI returned ${response.status}`,\n retryable: providerFailureRetryable(provider.kind),\n provider\n })\n )\n }\n\n const json = yield* parseOpenAiResponseJson(response)\n const parsed = yield* Schema.decodeUnknownEffect(OpenAiChatCompletionResponse)(json).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Invalid OpenAI response: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n const choice = parsed.choices[0]\n\n if (choice === undefined) {\n return yield* Effect.fail(\n new LLMError({\n cause: 'invalid_response',\n message: 'OpenAI response contained no choices',\n retryable: false\n })\n )\n }\n\n const events = yield* toLlmEvents(choice.message)\n\n if (parsed.usage === undefined) {\n return events\n }\n\n return [...events, LLMUsage.make({ usage: toAgentUsage(parsed.usage) })]\n }).pipe(Effect.withSpan('OpenAiProvider.stream'))\n\nexport const makeOpenAiProviderLayer = (config: OpenAiProviderConfig) =>\n Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n )\n\nexport const OpenAiProviderLayer = Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const config = yield* OpenAiConfig\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n).pipe(Layer.provide(Layer.mergeAll(OpenAiConfigLayer, FetchHttpClient.layer)))\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"provider.mjs","names":["LLMTextDelta"],"sources":["../../../src/providers/openai/provider.ts"],"sourcesContent":["import { Config, Context, Effect, Layer, Option, Redacted, Stream } from 'effect'\nimport {\n FetchHttpClient,\n HttpClient,\n HttpClientRequest,\n type HttpClientError,\n type HttpClientResponse\n} from 'effect/unstable/http'\nimport * as Schema from 'effect/Schema'\nimport {\n ToolCall,\n AgentInputUsage,\n AgentOutputUsage,\n AgentUsage,\n attachmentSourceText,\n attachmentSourceUrl,\n assistantContent,\n assistantHostToolCalls,\n isTextDocumentMimeType,\n messageContextText,\n replaceLoneSurrogatesDeep,\n prependMessageContextToContent,\n type AgentMessage,\n type Content,\n type ContentPart,\n type ToolDef\n} from '@yolk-sdk/agent/protocol'\nimport {\n LLMError,\n LLMDone,\n LLMProvider,\n LLMTextDelta,\n LLMToolCall,\n LLMUsage,\n type LLMEvent,\n type LLMRequest\n} from '@yolk-sdk/agent/loop'\nimport {\n classifyProviderFailure,\n providerErrorInfo,\n providerFailureCause,\n providerFailureRetryable\n} from '../provider-error.ts'\nimport { validateProviderTranscript } from '../transcript.ts'\n\ntype OpenAiProviderIdentity = {\n readonly id: string\n readonly name: string\n}\n\nexport type OpenAiProviderConfig = {\n readonly chatCompletionsUrl?: string\n readonly maxCompletionTokens: number\n /** Selects the compatible endpoint's output-limit parameter. Defaults to `max_completion_tokens`. */\n readonly completionTokenField?: 'max_completion_tokens' | 'max_tokens'\n readonly extraHeaders?: Readonly<Record<string, string>>\n /** Adds compatible endpoint extensions; canonical model/messages/limit/stream fields win. */\n readonly extraBody?: Readonly<Record<string, unknown>>\n /** Customizes safe error metadata for a branded OpenAI-compatible endpoint. */\n readonly providerIdentity?: OpenAiProviderIdentity\n readonly apiKey: Redacted.Redacted<string>\n}\n\ntype OpenAiTextContentPart = {\n readonly type: 'text'\n readonly text: string\n}\n\ntype OpenAiImageContentPart = {\n readonly type: 'image_url'\n readonly image_url: {\n readonly url: string\n }\n}\n\ntype OpenAiUserContent = string | ReadonlyArray<OpenAiTextContentPart | OpenAiImageContentPart>\n\ntype OpenAiToolCall = {\n readonly id: string\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly arguments: string\n }\n}\n\ntype OpenAiMessage =\n | { readonly role: 'system'; readonly content: string }\n | { readonly role: 'user'; readonly content: OpenAiUserContent }\n | {\n readonly role: 'assistant'\n readonly content: string | null\n readonly tool_calls?: ReadonlyArray<OpenAiToolCall>\n }\n | { readonly role: 'tool'; readonly tool_call_id: string; readonly content: string }\n\ntype OpenAiTool = {\n readonly type: 'function'\n readonly function: {\n readonly name: string\n readonly description: string\n readonly parameters: unknown\n }\n}\n\ntype OpenAiRequestBody = {\n readonly model: string\n readonly messages: ReadonlyArray<OpenAiMessage>\n readonly max_completion_tokens?: number\n readonly max_tokens?: number\n readonly stream: false\n readonly tools?: ReadonlyArray<OpenAiTool>\n readonly parallel_tool_calls?: true\n}\n\ntype OpenAiRequestBodyConfig = {\n readonly maxCompletionTokens: number\n readonly completionTokenField?: 'max_completion_tokens' | 'max_tokens'\n readonly extraBody?: Readonly<Record<string, unknown>>\n readonly providerName?: string\n}\n\nconst defaultOpenAiProviderIdentity: OpenAiProviderIdentity = {\n id: 'openai',\n name: 'OpenAI'\n}\n\nclass OpenAiFunctionResponse extends Schema.Class<OpenAiFunctionResponse>('OpenAiFunctionResponse')(\n {\n name: Schema.String,\n arguments: Schema.String\n }\n) {}\n\nclass OpenAiToolCallResponse extends Schema.Class<OpenAiToolCallResponse>('OpenAiToolCallResponse')(\n {\n id: Schema.String,\n type: Schema.Literals(['function']),\n function: OpenAiFunctionResponse\n }\n) {}\n\nclass OpenAiMessageResponse extends Schema.Class<OpenAiMessageResponse>('OpenAiMessageResponse')({\n content: Schema.NullOr(Schema.String),\n tool_calls: Schema.optional(Schema.Array(OpenAiToolCallResponse))\n}) {}\n\nclass OpenAiChoiceResponse extends Schema.Class<OpenAiChoiceResponse>('OpenAiChoiceResponse')({\n message: OpenAiMessageResponse,\n finish_reason: Schema.optional(Schema.NullOr(Schema.String))\n}) {}\n\nclass OpenAiPromptTokensDetails extends Schema.Class<OpenAiPromptTokensDetails>(\n 'OpenAiPromptTokensDetails'\n)({\n cached_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiCompletionTokensDetails extends Schema.Class<OpenAiCompletionTokensDetails>(\n 'OpenAiCompletionTokensDetails'\n)({\n reasoning_tokens: Schema.optional(Schema.Number)\n}) {}\n\nclass OpenAiUsageResponse extends Schema.Class<OpenAiUsageResponse>('OpenAiUsageResponse')({\n prompt_tokens: Schema.Number,\n completion_tokens: Schema.Number,\n prompt_tokens_details: Schema.optional(OpenAiPromptTokensDetails),\n completion_tokens_details: Schema.optional(OpenAiCompletionTokensDetails)\n}) {}\n\nclass OpenAiChatCompletionResponse extends Schema.Class<OpenAiChatCompletionResponse>(\n 'OpenAiChatCompletionResponse'\n)({\n choices: Schema.Array(OpenAiChoiceResponse),\n usage: Schema.optional(OpenAiUsageResponse)\n}) {}\n\nclass OpenAiConfig extends Context.Service<OpenAiConfig, OpenAiProviderConfig>()(\n '@app/OpenAiConfig'\n) {}\n\nconst OpenAiConfigLayer = Layer.effect(\n OpenAiConfig,\n Effect.gen(function* () {\n const apiKey = yield* Config.redacted('OPENAI_API_KEY')\n const maxCompletionTokens = yield* Config.int('OPENAI_MAX_COMPLETION_TOKENS')\n return { apiKey, maxCompletionTokens }\n }).pipe(\n Effect.mapError(\n () =>\n new LLMError({\n cause: 'provider_error',\n message: 'OpenAI provider environment configuration missing',\n retryable: false\n })\n )\n )\n)\n\nconst unknownToMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error)\n\nconst encodeJsonString = (value: unknown, message: string) =>\n Schema.encodeUnknownEffect(Schema.UnknownFromJsonString)(value).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst decodeJsonString = (raw: string, message: string) =>\n Schema.decodeUnknownEffect(Schema.UnknownFromJsonString)(raw).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `${message}: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n\nconst unsupportedContentError = (contentType: string, providerName: string) =>\n new LLMError({\n cause: 'provider_error',\n message: `${contentType} content is not supported by the ${providerName} provider yet`,\n retryable: false\n })\n\nconst textDocumentToOpenAiPart = (\n part: Extract<ContentPart, { readonly _tag: 'Document' }>,\n providerName: string\n) =>\n attachmentSourceText(part.source).pipe(\n Effect.mapError(() => unsupportedContentError('Invalid document text', providerName)),\n Effect.flatMap(\n Option.match({\n onNone: () =>\n Effect.fail(unsupportedContentError('Unresolved document source', providerName)),\n onSome: text => {\n const block: OpenAiTextContentPart = {\n type: 'text',\n text: `Document: ${part.title ?? part.filename}\\n\\n${text}`\n }\n\n return Effect.succeed(block)\n }\n })\n )\n )\n\nconst contentPartToUserPart = (\n part: ContentPart,\n providerName: string\n): Effect.Effect<OpenAiTextContentPart | OpenAiImageContentPart, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed({ type: 'text', text: part.text })\n case 'Image':\n return Option.match(attachmentSourceUrl(part.source, part.mimeType), {\n onNone: () => Effect.fail(unsupportedContentError('Unresolved image source', providerName)),\n onSome: url => Effect.succeed({ type: 'image_url', image_url: { url } })\n })\n case 'Document':\n return isTextDocumentMimeType(part.mimeType)\n ? textDocumentToOpenAiPart(part, providerName)\n : Effect.fail(unsupportedContentError('Document', providerName))\n case 'Audio':\n return Effect.fail(unsupportedContentError('Audio', providerName))\n }\n}\n\nconst contentToUserContent = (\n content: Content,\n providerName: string\n): Effect.Effect<OpenAiUserContent, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, part => contentPartToUserPart(part, providerName))\n\nconst contentPartToText = (\n part: ContentPart,\n owner: string,\n providerName: string\n): Effect.Effect<string, LLMError> => {\n switch (part._tag) {\n case 'Text':\n return Effect.succeed(part.text)\n case 'Image':\n return Effect.fail(unsupportedContentError(`${owner} image`, providerName))\n case 'Document':\n return Effect.fail(unsupportedContentError(`${owner} document`, providerName))\n case 'Audio':\n return Effect.fail(unsupportedContentError(`${owner} audio`, providerName))\n }\n}\n\nconst contentToText = (\n content: Content,\n owner: string,\n providerName: string\n): Effect.Effect<string, LLMError> =>\n typeof content === 'string'\n ? Effect.succeed(content)\n : Effect.forEach(content, part => contentPartToText(part, owner, providerName)).pipe(\n Effect.map(textParts => textParts.join('\\n'))\n )\n\nconst serializeToolArguments = (params: unknown, providerName: string) =>\n encodeJsonString(params, `Could not serialize ${providerName} tool arguments`)\n\nconst toolCallToOpenAiToolCall = (\n call: ToolCall,\n providerName: string\n): Effect.Effect<OpenAiToolCall, LLMError> =>\n Effect.gen(function* () {\n return {\n id: call.id,\n type: 'function',\n function: {\n name: call.name,\n arguments: yield* serializeToolArguments(call.params, providerName)\n }\n }\n })\n\nconst toOpenAiMessage = (\n message: AgentMessage,\n providerName: string\n): Effect.Effect<OpenAiMessage, LLMError> =>\n Effect.gen(function* () {\n switch (message._tag) {\n case 'User':\n return {\n role: 'user',\n content: yield* contentToUserContent(\n prependMessageContextToContent(message.content, messageContextText(message)),\n providerName\n )\n }\n case 'Assistant': {\n const content = prependMessageContextToContent(\n assistantContent(message),\n messageContextText(message)\n )\n const toolCalls = yield* Effect.forEach(assistantHostToolCalls(message), call =>\n toolCallToOpenAiToolCall(call, providerName)\n )\n\n if (toolCalls.length > 0) {\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant', providerName),\n tool_calls: toolCalls\n }\n }\n\n return {\n role: 'assistant',\n content: yield* contentToText(content, 'Assistant', providerName)\n }\n }\n case 'ToolResult':\n return {\n role: 'tool',\n tool_call_id: message.toolCallId,\n content: yield* contentToText(\n prependMessageContextToContent(message.content, messageContextText(message)),\n 'Tool result',\n providerName\n )\n }\n }\n })\n\nconst toOpenAiTool = (tool: ToolDef): OpenAiTool => ({\n type: 'function',\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters\n }\n})\n\nexport const toOpenAiRequestBody = (\n request: LLMRequest,\n config: OpenAiRequestBodyConfig\n): Effect.Effect<OpenAiRequestBody, LLMError> =>\n Effect.gen(function* () {\n const providerName = config.providerName ?? defaultOpenAiProviderIdentity.name\n\n if (!Number.isSafeInteger(config.maxCompletionTokens) || config.maxCompletionTokens <= 0) {\n return yield* Effect.fail(\n new LLMError({\n cause: 'validation_error',\n message: `${providerName} maxCompletionTokens must be a positive safe integer`,\n retryable: false\n })\n )\n }\n\n yield* validateProviderTranscript(request.messages)\n const systemMessage: OpenAiMessage = { role: 'system', content: request.systemPrompt }\n const requestMessages = yield* Effect.forEach(request.messages, message =>\n toOpenAiMessage(message, providerName)\n )\n const messages = [systemMessage, ...requestMessages]\n\n const completionTokenLimit =\n config.completionTokenField === 'max_tokens'\n ? { max_tokens: config.maxCompletionTokens }\n : { max_completion_tokens: config.maxCompletionTokens }\n const body: OpenAiRequestBody = {\n ...config.extraBody,\n model: request.model,\n messages,\n ...completionTokenLimit,\n stream: false\n }\n\n if (request.tools.length === 0) {\n return body\n }\n\n return {\n ...body,\n tools: request.tools.map(toOpenAiTool),\n parallel_tool_calls: true\n }\n })\n\nconst parseToolArguments = (raw: string, providerName: string) =>\n decodeJsonString(raw, `Invalid ${providerName} tool arguments JSON`)\n\nconst toLlmEvents = (\n choice: OpenAiChoiceResponse,\n providerIdentity: OpenAiProviderIdentity\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n if (choice.finish_reason === 'length' || choice.finish_reason === 'content_filter') {\n return yield* Effect.fail(\n new LLMError({\n cause: 'invalid_response',\n message: `${providerIdentity.name} response stopped with ${choice.finish_reason}`,\n retryable: false,\n provider: providerErrorInfo({\n provider: providerIdentity.id,\n kind: 'invalid_response',\n providerCode: choice.finish_reason\n })\n })\n )\n }\n\n const content = choice.message.content ?? ''\n const textEvents = content.length > 0 ? [LLMTextDelta.make({ text: content })] : []\n const toolCallEvents = yield* Effect.forEach(choice.message.tool_calls ?? [], call =>\n parseToolArguments(call.function.arguments, providerIdentity.name).pipe(\n Effect.map(params =>\n LLMToolCall.make({\n call: ToolCall.make({\n id: call.id,\n name: call.function.name,\n params\n })\n })\n )\n )\n )\n\n if (toolCallEvents.length > 0) {\n return [...textEvents, ...toolCallEvents, LLMDone.make({ stopReason: 'tool_use' })]\n }\n\n return [...textEvents, LLMDone.make({ stopReason: 'stop' })]\n })\n\nconst toAgentUsage = (usage: OpenAiUsageResponse) =>\n AgentUsage.make({\n input: AgentInputUsage.make({\n total: usage.prompt_tokens,\n uncached: usage.prompt_tokens - (usage.prompt_tokens_details?.cached_tokens ?? 0),\n cacheRead: usage.prompt_tokens_details?.cached_tokens\n }),\n output: AgentOutputUsage.make({\n total: usage.completion_tokens,\n reasoning: usage.completion_tokens_details?.reasoning_tokens,\n text: usage.completion_tokens - (usage.completion_tokens_details?.reasoning_tokens ?? 0)\n })\n })\n\nconst toHttpClientLlmError =\n (providerIdentity: OpenAiProviderIdentity, retryable: boolean) =>\n (error: HttpClientError.HttpClientError) =>\n new LLMError({\n cause: 'provider_error',\n message: `${providerIdentity.name} request failed: ${error.message}`,\n retryable,\n provider: providerErrorInfo({\n provider: providerIdentity.id,\n kind: retryable ? 'network' : 'unknown'\n })\n })\n\nconst parseOpenAiResponseJson = (\n response: HttpClientResponse.HttpClientResponse,\n providerName: string\n): Effect.Effect<unknown, LLMError> =>\n response.json.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Could not parse ${providerName} response JSON: ${error.message}`,\n retryable: false\n })\n )\n )\n\nconst sendOpenAiRequest = (\n config: OpenAiProviderConfig,\n request: LLMRequest,\n client: HttpClient.HttpClient\n): Effect.Effect<ReadonlyArray<LLMEvent>, LLMError> =>\n Effect.gen(function* () {\n const providerIdentity = config.providerIdentity ?? defaultOpenAiProviderIdentity\n const body = yield* toOpenAiRequestBody(request, {\n maxCompletionTokens: config.maxCompletionTokens,\n providerName: providerIdentity.name,\n ...(config.completionTokenField === undefined\n ? {}\n : { completionTokenField: config.completionTokenField }),\n ...(config.extraBody === undefined ? {} : { extraBody: config.extraBody })\n })\n // Replayed transcripts can carry lone surrogates; harden the lowered\n // body so one bad historical string cannot poison every model call.\n const serializedBody = yield* encodeJsonString(\n replaceLoneSurrogatesDeep(body),\n `Could not serialize ${providerIdentity.name} request`\n )\n\n const httpRequest = HttpClientRequest.post(\n config.chatCompletionsUrl ?? 'https://api.openai.com/v1/chat/completions'\n ).pipe(\n HttpClientRequest.setHeaders({\n ...config.extraHeaders,\n accept: 'application/json',\n authorization: `Bearer ${Redacted.value(config.apiKey)}`,\n 'content-type': 'application/json'\n }),\n HttpClientRequest.bodyText(serializedBody, 'application/json')\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError(toHttpClientLlmError(providerIdentity, true)))\n\n if (response.status < 200 || response.status >= 300) {\n const errorText = yield* response.text.pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'provider_error',\n message: `Could not read ${providerIdentity.name} error body: ${error.message}`,\n retryable: false\n })\n )\n )\n\n const provider = classifyProviderFailure({\n provider: providerIdentity.id,\n status: response.status,\n headers: response.headers,\n body: errorText\n })\n\n return yield* Effect.fail(\n new LLMError({\n cause: providerFailureCause(provider.kind),\n message: `${providerIdentity.name} returned ${response.status}`,\n retryable: providerFailureRetryable(provider.kind),\n provider\n })\n )\n }\n\n const json = yield* parseOpenAiResponseJson(response, providerIdentity.name)\n const parsed = yield* Schema.decodeUnknownEffect(OpenAiChatCompletionResponse)(json).pipe(\n Effect.mapError(\n error =>\n new LLMError({\n cause: 'invalid_response',\n message: `Invalid ${providerIdentity.name} response: ${unknownToMessage(error)}`,\n retryable: false\n })\n )\n )\n const choice = parsed.choices[0]\n\n if (choice === undefined) {\n return yield* Effect.fail(\n new LLMError({\n cause: 'invalid_response',\n message: `${providerIdentity.name} response contained no choices`,\n retryable: false\n })\n )\n }\n\n const events = yield* toLlmEvents(choice, providerIdentity)\n\n if (parsed.usage === undefined) {\n return events\n }\n\n return [...events, LLMUsage.make({ usage: toAgentUsage(parsed.usage) })]\n }).pipe(\n Effect.withSpan(`${config.providerIdentity?.id ?? defaultOpenAiProviderIdentity.id}.stream`)\n )\n\nexport const makeOpenAiProviderLayer = (config: OpenAiProviderConfig) =>\n Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n )\n\nexport const OpenAiProviderLayer = Layer.effect(\n LLMProvider,\n Effect.gen(function* () {\n const config = yield* OpenAiConfig\n const client = yield* HttpClient.HttpClient\n\n return LLMProvider.of({\n stream: request =>\n Stream.fromEffect(sendOpenAiRequest(config, request, client)).pipe(\n Stream.flatMap(events => Stream.fromIterable(events))\n )\n })\n })\n).pipe(Layer.provide(Layer.mergeAll(OpenAiConfigLayer, FetchHttpClient.layer)))\n"],"mappings":"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"}
|