@yourgpt/llm-sdk 2.6.0-beta.2 → 2.6.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.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -17
  3. package/dist/adapters/index.d.mts +11 -13
  4. package/dist/adapters/index.d.ts +11 -13
  5. package/dist/adapters/index.js +406 -114
  6. package/dist/adapters/index.mjs +406 -114
  7. package/dist/audio-K-Z3-Bce.d.mts +191 -0
  8. package/dist/audio-K-Z3-Bce.d.ts +191 -0
  9. package/dist/{base-BICaQaRS.d.mts → base-6LzczuCg.d.ts} +8 -1
  10. package/dist/{base-CpRPB8u8.d.ts → base-DySbTP8C.d.mts} +8 -1
  11. package/dist/decisions-CJzOt87t.d.mts +137 -0
  12. package/dist/decisions-CJzOt87t.d.ts +137 -0
  13. package/dist/fallback/index.d.mts +4 -4
  14. package/dist/fallback/index.d.ts +4 -4
  15. package/dist/http-BQxUT5A9.d.mts +9 -0
  16. package/dist/http-BQxUT5A9.d.ts +9 -0
  17. package/dist/index.d.mts +17 -9
  18. package/dist/index.d.ts +17 -9
  19. package/dist/index.js +698 -205
  20. package/dist/index.mjs +695 -206
  21. package/dist/providers/anthropic/index.d.mts +7 -4
  22. package/dist/providers/anthropic/index.d.ts +7 -4
  23. package/dist/providers/anthropic/index.js +389 -115
  24. package/dist/providers/anthropic/index.mjs +389 -115
  25. package/dist/providers/azure/index.d.mts +3 -3
  26. package/dist/providers/azure/index.d.ts +3 -3
  27. package/dist/providers/cloudflare/index.d.mts +11 -0
  28. package/dist/providers/cloudflare/index.d.ts +11 -0
  29. package/dist/providers/cloudflare/index.js +743 -0
  30. package/dist/providers/cloudflare/index.mjs +741 -0
  31. package/dist/providers/fireworks/index.d.mts +3 -3
  32. package/dist/providers/fireworks/index.d.ts +3 -3
  33. package/dist/providers/fireworks/index.js +181 -60
  34. package/dist/providers/fireworks/index.mjs +181 -60
  35. package/dist/providers/google/index.d.mts +3 -3
  36. package/dist/providers/google/index.d.ts +3 -3
  37. package/dist/providers/google/index.js +181 -60
  38. package/dist/providers/google/index.mjs +181 -60
  39. package/dist/providers/ollama/index.d.mts +4 -4
  40. package/dist/providers/ollama/index.d.ts +4 -4
  41. package/dist/providers/openai/index.d.mts +38 -6
  42. package/dist/providers/openai/index.d.ts +38 -6
  43. package/dist/providers/openai/index.js +1451 -85
  44. package/dist/providers/openai/index.mjs +1449 -86
  45. package/dist/providers/openrouter/index.d.mts +21 -4
  46. package/dist/providers/openrouter/index.d.ts +21 -4
  47. package/dist/providers/openrouter/index.js +1040 -131
  48. package/dist/providers/openrouter/index.mjs +1040 -132
  49. package/dist/providers/togetherai/index.d.mts +3 -3
  50. package/dist/providers/togetherai/index.d.ts +3 -3
  51. package/dist/providers/togetherai/index.js +181 -60
  52. package/dist/providers/togetherai/index.mjs +181 -60
  53. package/dist/providers/typesafe/index.d.mts +8 -0
  54. package/dist/providers/typesafe/index.d.ts +8 -0
  55. package/dist/providers/typesafe/index.js +735 -0
  56. package/dist/providers/typesafe/index.mjs +733 -0
  57. package/dist/providers/xai/index.d.mts +3 -3
  58. package/dist/providers/xai/index.d.ts +3 -3
  59. package/dist/providers/xai/index.js +181 -60
  60. package/dist/providers/xai/index.mjs +181 -60
  61. package/dist/{types-DCFqkJAy.d.ts → types-B0y6VZSQ.d.ts} +1 -1
  62. package/dist/{types-DHQEMFHA.d.mts → types-BXQMtaCX.d.ts} +17 -3
  63. package/dist/{types-C3RgRbEC.d.mts → types-BY6BVhFg.d.mts} +1 -1
  64. package/dist/{types-Dikj3En4.d.ts → types-C7_qVSSd.d.mts} +17 -3
  65. package/dist/{types-Dg1Bcg1k.d.mts → types-D7lzMMLu.d.mts} +13 -1
  66. package/dist/{types-Dg1Bcg1k.d.ts → types-D7lzMMLu.d.ts} +13 -1
  67. package/dist/yourgpt/index.d.mts +1 -1
  68. package/dist/yourgpt/index.d.ts +1 -1
  69. package/docs/decisions.md +174 -0
  70. package/docs/media.md +201 -0
  71. package/docs/multimodal-and-audio.md +9 -2
  72. package/package.json +34 -12
  73. package/dist/audio-BBSwnjo8.d.mts +0 -92
  74. package/dist/audio-BBSwnjo8.d.ts +0 -92
@@ -0,0 +1,174 @@
1
+ # Native Decisions
2
+
3
+ Use Decisions to classify or score shared state with native decision models.
4
+ Chat generation, Responses, and tool execution keep their existing APIs.
5
+ See [package availability](https://copilot-sdk.yourgpt.ai/docs/llm-sdk/beta-upgrade)
6
+ for the versions that expose these exports.
7
+
8
+ ```ts
9
+ import { experimental_decide } from "@yourgpt/llm-sdk";
10
+ import { openaiDecision } from "@yourgpt/llm-sdk/openai";
11
+
12
+ const result = await experimental_decide({
13
+ model: openaiDecision("gpt-6-luna"), // server-side OPENAI_API_KEY
14
+ state: { ticket: "Every customer is unable to sign in." },
15
+ questions: {
16
+ outage: {
17
+ type: "boolean",
18
+ instructions: "Does the ticket report a service outage?",
19
+ },
20
+ team: {
21
+ type: "choice",
22
+ instructions: "Which team should handle this?",
23
+ criteria: {
24
+ billing: "Invoices and refunds",
25
+ engineering: "Service outages",
26
+ },
27
+ },
28
+ impact: {
29
+ type: "score",
30
+ instructions: "How much service is unavailable?",
31
+ criteria: ["None", "Some features or users", "All users"],
32
+ },
33
+ },
34
+ timeoutMs: 60_000,
35
+ });
36
+
37
+ if (result.answers.team.type === "refusal") {
38
+ // Route to your application's review or error path.
39
+ } else {
40
+ const team: "billing" | "engineering" = result.answers.team.choice;
41
+ console.log(team, result.answers.team.probabilities);
42
+ }
43
+ ```
44
+
45
+ Question IDs and choice keys are preserved and inferred in TypeScript. Boolean
46
+ answers contain `probability` (P(true)); scores are fractional, zero-based positions
47
+ along the ordered criteria. Choice/score `confidence` is present only when supplied
48
+ by the provider. Confidence and probability are different signals; neither promises
49
+ task accuracy. Define and evaluate application-specific routing thresholds yourself.
50
+
51
+ ## Providers
52
+
53
+ | Factory / import | Default model | Environment | Native endpoint |
54
+ | --------------------------------------- | --------------------- | ------------------------------------------------ | -------------------------------------- |
55
+ | `openaiDecision` from `/openai` | `gpt-6-luna` | `OPENAI_API_KEY` | `/v1/decisions` |
56
+ | `typesafeDecision` from `/typesafe` | `jev-latest` | `TYPESAFE_AI_API_KEY` | `/v1/systemone` |
57
+ | `openrouterDecision` from `/openrouter` | `typesafe/jev-1.13` | `OPENROUTER_API_KEY` | `/api/alpha/decisions` |
58
+ | `cloudflareDecision` from `/cloudflare` | `@cf/cloudflare/clef` | `CLOUDFLARE_AUTH_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` | Account-scoped Workers AI run endpoint |
59
+
60
+ Factories also accept `{ apiKey, baseUrl, fetch, headers }` as their second argument.
61
+ Cloudflare accepts `accountId` and the `@cf/cloudflare/clef-flash` model. The
62
+ OpenRouter Decisions base URL is `https://openrouter.ai/api/alpha`; do not pass its
63
+ chat `/api/v1` base. Cloudflare's custom base must include the account's `/ai/run`
64
+ path. Supply keys on the server; these factories do not require upgrading the
65
+ optional OpenAI npm client.
66
+
67
+ No Anthropic native Decisions adapter is provided. Continue using its existing
68
+ chat/structured-output APIs where appropriate; they are not silently substituted
69
+ for native Decisions.
70
+
71
+ ## Inputs and limits
72
+
73
+ `state` and `instructions` accept text, JSON objects, or JSON arrays. Arrays remain
74
+ structured JSON. Numbers must be finite; cycles, undefined values, and nesting
75
+ beyond 64 levels are rejected. Unknown request options, such as `temperature`,
76
+ tools, streaming, or audio, fail explicitly.
77
+
78
+ - Boolean criteria may describe `true` and/or `false`. OpenRouter requires both
79
+ descriptions if criteria are provided.
80
+ - Choice criteria require 2–255 distinct string keys. Native OpenAI boolean-valued
81
+ choices are outside this common API; use a boolean question instead.
82
+ - Score criteria require at least two levels. System One adapters accept up to ten.
83
+ - OpenAI accepts at most 200 questions. Cloudflare accepts at most 64 and requires
84
+ IDs of 1–100 letters, digits, underscores, dots, or hyphens.
85
+ - Request bodies are limited to 32 MiB by this SDK, or 13 MiB for Cloudflare.
86
+
87
+ Optional provider settings are checked rather than silently ignored:
88
+
89
+ ```ts
90
+ const providerOptions = {
91
+ openai: { safetyIdentifier: "user-123", promptCacheKey: "support-v1" },
92
+ };
93
+ // Jev: { typesafe: { longContext: true } }
94
+ // OpenRouter: { openrouter: { sessionId: "session-1",
95
+ // provider: { order: ["TypeSafe"], allowFallbacks: false } } }
96
+ ```
97
+
98
+ Only pass the namespace matching the selected provider. Long-context support is
99
+ experimental and depends on the Jev model/account. Cache hits are not guaranteed.
100
+
101
+ ## Image state
102
+
103
+ ```ts
104
+ import { readFile } from "node:fs/promises";
105
+
106
+ const images = [
107
+ {
108
+ data: await readFile("./sample.png"),
109
+ mediaType: "image/png" as const,
110
+ },
111
+ ];
112
+ // Pass `images` alongside `state` and `questions` to experimental_decide.
113
+ ```
114
+
115
+ OpenAI accepts PNG, JPEG, WebP, and GIF bytes with optional `detail`, encoded as
116
+ inline data URLs on the wire. This SDK accepts bytes only. OpenAI’s current
117
+ native endpoint also accepts hosted image URLs; URL inputs are not yet exposed
118
+ by this SDK. File IDs remain unsupported by the native endpoint. This SDK limits inline files to 20 MiB and 128 images.
119
+ It never downloads URLs itself. Jev accepts PNG/JPEG bytes up to 16 MiB each using referenced
120
+ native image assets and its required experimental image header. The original state
121
+ is retained under `state` and image references under `images` in the native Jev
122
+ request. Cloudflare accepts at most four PNG/JPEG/WebP files, 4 MiB each and 8 MiB
123
+ total. Remote URLs fail locally for every Decisions provider. OpenAI-specific
124
+ detail settings fail on Jev/Cloudflare. Tool image outputs on Responses are a
125
+ separate API and can still use hosted URLs or OpenAI file IDs.
126
+
127
+ Byte format headers and size limits are checked locally. Providers perform full
128
+ image decoding, dimension and animation validation. Jev requires still images with
129
+ each dimension between 280 and 1568 pixels; Cloudflare limits each image to 16
130
+ megapixels. Prepare images before sending; this SDK does not resize them.
131
+ OpenRouter's documented native Decisions request has no image field, so this
132
+ adapter rejects image inputs.
133
+
134
+ ## Results, refusals, and errors
135
+
136
+ The SDK rejects missing/extra answers, duplicate OpenAI answer IDs, unknown choices,
137
+ incomplete distributions, invalid probabilities, and scores inconsistent with their
138
+ ordered levels. Values are never renormalized. TypeSafe's documented rounding is
139
+ allowed for direct Jev and confirmed TypeSafe responses through OpenRouter.
140
+
141
+ A named OpenAI refusal remains `{ type: "refusal" }` under that question; other
142
+ answers remain available. An unnamed refusal throws `DecisionAPIError` with code
143
+ `refusal`. HTTP, network, malformed-response, unsupported-option, and input errors
144
+ are separate. Errors do not include raw provider error bodies or credentials.
145
+ Use `DecisionAPIError.isInstance(error)` to recognize these errors across separately
146
+ bundled provider imports, CommonJS, and ES modules.
147
+
148
+ Pass `signal` to cancel. The timeout covers the response body too, and defaults to
149
+ 60 seconds. Errors expose `status`, `requestId` when available, and a `retryable`
150
+ hint. There are **no automatic retries or chat fallbacks**. `TRAINING_CONFLICT` and
151
+ existing guardrail behavior are unchanged; evaluate a migration separately.
152
+
153
+ Normalized token/cache/cost usage is in `usage`; absent fields remain absent.
154
+ `response.body` retains the original provider response, including gateway metadata
155
+ and native score legends. Avoid logging it if your state contains private data.
156
+ Responses larger than 8 MiB are rejected.
157
+
158
+ ## Example and verification
159
+
160
+ The existing skills demo includes `server/decisions.ts`. With the required provider
161
+ key already in your process environment, run from `examples/skills-demo`:
162
+
163
+ ```sh
164
+ pnpm demo:decisions openai
165
+ pnpm demo:decisions openrouter
166
+ ```
167
+
168
+ The example uses a synthetic ticket and a review route for uncertain/refused
169
+ answers. It does not load a `.env` file automatically or change the chat server.
170
+ Opt-in live tests are in `test/live/decisions.cjs`; select a provider with
171
+ `DECISION_TEST_PROVIDER` and enable `RUN_DECISIONS_LIVE=1` with its key configured
172
+ privately. Direct Jev/Cloudflare live checks require their own credentials.
173
+
174
+ Research and source pins: [implementation research](../../../docs/plans/2026-10-17-next-beta.md).
package/docs/media.md ADDED
@@ -0,0 +1,201 @@
1
+ # Images and realtime voice
2
+
3
+ Generate and edit images with OpenAI, or connect browser voice calls to Copilot
4
+ tools over WebRTC. Realtime voice is experimental. See
5
+ [package availability](https://copilot-sdk.yourgpt.ai/docs/llm-sdk/beta-upgrade)
6
+ for the versions that expose these APIs.
7
+
8
+ ## Image generation and editing
9
+
10
+ ```ts
11
+ import { generateImage } from "@yourgpt/llm-sdk";
12
+ import { openaiImage } from "@yourgpt/llm-sdk/openai";
13
+
14
+ const result = await generateImage({
15
+ model: openaiImage("gpt-image-2.5-flare"), // reads OPENAI_API_KEY on the server
16
+ prompt: "A simple blue circle on a white background",
17
+ size: "1024x1024",
18
+ providerOptions: { openai: { quality: "low", outputFormat: "png" } },
19
+ });
20
+ // result.image.uint8Array, result.image.base64, result.image.mediaType
21
+ // result.images, result.usage, result.response.requestId
22
+
23
+ const edit = await generateImage({
24
+ model: openaiImage("gpt-image-2.5-flare"),
25
+ prompt: "Change the circle to green",
26
+ images: [{ data: result.image.uint8Array, mediaType: "image/png" }],
27
+ providerOptions: { openai: { outputFormat: "webp", outputCompression: 80 } },
28
+ });
29
+ ```
30
+
31
+ `images` selects the Images edit endpoint. Inputs accept bytes, ArrayBuffer or Blob
32
+ with a PNG/JPEG/WebP media type. An optional PNG `mask` applies to the first image;
33
+ match its dimensions to that image. No remote URLs are fetched automatically.
34
+ `n` is 1–10; editing accepts 1–16 images. Each input must be below 50 MB, and the
35
+ SDK limits total input to 100 MB and the encoded response to 128 MiB.
36
+
37
+ OpenAI options: `quality`, `background`, `outputFormat`, `outputCompression`,
38
+ `inputFidelity` (edits), `moderation` (generation), and `safetyIdentifier`.
39
+ GPT Image 2.5 supports `xhigh`/`max` quality and custom dimensions within its
40
+ documented limits. Transparent output requires PNG or WebP. Compression requires
41
+ JPEG or WebP. Unsupported options fail explicitly. Retired DALL-E models are not
42
+ supported. Streaming partial images and the Responses-hosted image tool are
43
+ separate APIs, not part of this helper.
44
+
45
+ `signal` cancels requests; `timeoutMs` defaults to 120 seconds (maximum 10 minutes).
46
+ Requests are not automatically retried. Aborting locally cannot guarantee that a
47
+ provider has stopped a billable generation. `MediaAPIError` includes `code`,
48
+ optional HTTP `status`, and `requestId`; it omits provider error bodies.
49
+
50
+ ## Realtime voice: server setup
51
+
52
+ OpenAI GA Realtime is a turn-based speech conversation over WebRTC. It is distinct
53
+ from GPT-Live's continuous/delegated protocol. The SDK supports OpenAI Realtime
54
+ browser calls, not GPT-Live, telephony/SIP, or other realtime providers.
55
+
56
+ Authenticate and authorize your session endpoint, apply quotas/rate limits, and
57
+ set `Cache-Control: no-store`. Keep the long-lived key on your server. The easiest
58
+ flow exchanges the browser's SDP offer on that server:
59
+
60
+ ```ts
61
+ import { openaiRealtime } from "@yourgpt/llm-sdk/openai";
62
+
63
+ // Inside your authenticated POST handler. Only accept the SDP from the browser.
64
+ const answer = await openaiRealtime("gpt-realtime-2.1").createCall({
65
+ sdp: requestBody.sdp,
66
+ instructions: "You are a helpful assistant. Keep spoken answers brief.",
67
+ voice: "marin",
68
+ transcriptionModel: "gpt-transcribe",
69
+ tools: [
70
+ {
71
+ name: "get_status",
72
+ description: "Read the application status",
73
+ parameters: {
74
+ type: "object",
75
+ properties: {},
76
+ additionalProperties: false,
77
+ },
78
+ },
79
+ ],
80
+ signal: requestSignal,
81
+ });
82
+ // Return answer as JSON: { sdp: "..." }. No provider key is returned.
83
+ ```
84
+
85
+ Alternatively, `createClientSecret({ expiresAfterSeconds: 60, ...sessionOptions })`
86
+ returns `{ value, expiresAt, modelId }`. Give only that short-lived credential to
87
+ the browser's `getClientSecret` callback. The TTL applies to establishing sessions,
88
+ not to an already connected call; a token can establish multiple calls until it
89
+ expires. Session options attached to a token can be overridden by the client.
90
+ Neither flow substitutes for authentication/authorization on business tool APIs.
91
+
92
+ Optional server settings include `outputModality: "text" | "audio"`,
93
+ `maxOutputTokens`, `language` with a transcription model, and `safetyIdentifier`.
94
+ Setup requests time out after 30 seconds and are never retried automatically.
95
+ Server VAD with automatic replies/interruption is enabled by default. Select
96
+ semantic VAD with `turnDetection: { type: "semantic_vad", eagerness: "medium" }`,
97
+ or use `turnDetection: null` with explicit `commitAudio()` after capturing audio.
98
+
99
+ ## Browser and React
100
+
101
+ Use `useRealtimeVoice` inside `CopilotProvider`. It reads the active tool registry
102
+ and `needsApproval` policy from that provider. Supply `getToolPermission` to reuse
103
+ your application’s stored permissions; otherwise approval-required tools ask on every call. Start capture from a user gesture:
104
+
105
+ ```tsx
106
+ import { useRealtimeVoice } from "@yourgpt/copilot-sdk/react";
107
+
108
+ function VoiceControls() {
109
+ const voice = useRealtimeVoice({
110
+ createCall: async ({ sdp, signal }) => {
111
+ const response = await fetch("/api/voice", {
112
+ method: "POST",
113
+ headers: { "Content-Type": "application/json" },
114
+ body: JSON.stringify({ sdp }),
115
+ signal,
116
+ });
117
+ if (!response.ok) throw new Error("Voice setup failed");
118
+ return response.json();
119
+ },
120
+ });
121
+ return (
122
+ <>
123
+ <button onClick={() => void voice.connect().catch(console.error)}>
124
+ Start call
125
+ </button>
126
+ <button onClick={voice.disconnect}>End call</button>
127
+ <button onClick={() => voice.setMuted(!voice.muted)}>
128
+ Toggle microphone
129
+ </button>
130
+ {voice.pendingApprovals.map((call) => (
131
+ <div key={call.id}>
132
+ Allow {call.name}?
133
+ <button onClick={() => call.approve()}>Allow</button>
134
+ <button onClick={call.reject}>Deny</button>
135
+ </div>
136
+ ))}
137
+ </>
138
+ );
139
+ }
140
+ ```
141
+
142
+ Also available: `state`, `muted`, `error`, `transcripts`, `sendText`, `interrupt`,
143
+ `commitAudio`, and `resumeAudio` (for browsers that block autoplay). Unmounting
144
+ disconnects and releases capture. The hook retains at most 200 transcript items.
145
+ Connection configuration is captured when connecting; tool handlers, the optional permission resolver,
146
+ and observer callbacks use the latest React values.
147
+
148
+ Framework-independent apps can construct `RealtimeVoiceSession` from
149
+ `@yourgpt/copilot-sdk/core`, with `getTools`, `getToolPermission` and `onApproval`.
150
+ `realtimeToolDefinitions(tools)` converts Copilot tools to the server setup shape;
151
+ the server must independently choose which tools it permits. Credentials and
152
+ session instructions must not be accepted blindly from browser input.
153
+
154
+ `needsApproval` (including conditional callbacks) is respected. `deny_always`
155
+ blocks execution; `allow_always`/`session` permit it. Without an approval callback,
156
+ the core class denies required approval. The React hook exposes pending approvals
157
+ unless you provide your own callback. Tool handlers receive `signal`, `toolCallId`
158
+ and approved `approvalData`. Business handlers still validate their arguments and
159
+ enforce server-side authorization. These are voice approval controls, not the
160
+ text-chat pending-approval queue.
161
+
162
+ Duplicate call IDs execute at most once within a connection. Completed tool batches
163
+ resume the model once; a user interruption suppresses stale follow-up speech while
164
+ retaining tool output in the active conversation. Failed/timed-out tools produce
165
+ one error result. A disconnect cancels pending approvals and signals running tools;
166
+ handlers must honor cancellation themselves. There is no automatic reconnect or
167
+ tool replay. Reconnecting creates a fresh provider conversation; persist application
168
+ state separately if needed. This is not a cross-session exactly-once guarantee.
169
+
170
+ ## Limits and observability
171
+
172
+ - HTTPS or localhost and a WebRTC browser are required. The default microphone
173
+ adapter uses browser permission; an optional `getMicrophone` adapter can supply
174
+ synthetic audio. The session owns and stops all tracks returned by that adapter.
175
+ - Audio travels as WebRTC media tracks with negotiated codecs/rates, not as an SDK
176
+ PCM stream. Use browser `RTCPeerConnection.getStats()` for actual negotiated media
177
+ metrics. WebRTC's server buffer handles VAD interruption and truncation.
178
+ - `onTranscript`, `onUsage`, `onEvent`, `onToolResult`, `onStateChange`, and `onError`
179
+ provide observations. Raw events can contain conversation content: log selectively.
180
+ - Defaults: 30-second connection timeout, 60-second tool/approval timeout,
181
+ 30-minute call duration, 100 tool calls, 1 MiB event/send buffer, 64 KB tool result.
182
+ Limits are configurable through `RealtimeVoiceOptions` where exposed.
183
+ - Network loss closes the session and stops capture. Browser autoplay failures
184
+ keep the call available for `resumeAudio()`. Clearly disclose AI-generated voice.
185
+
186
+ Run the local demo in `examples/skills-demo` with `pnpm demo:media`, then visit
187
+ `http://localhost:3033/media.html`. Existing `.env` keys are read only by the server;
188
+ never prefix them with `VITE_`. Its server binds to loopback and checks the origin;
189
+ that local-only guard is not production authentication.
190
+
191
+ ## Research and validation
192
+
193
+ Provider contracts: [Realtime getting started](https://developers.openai.com/api/docs/guides/realtime),
194
+ [WebRTC](https://developers.openai.com/api/docs/guides/voice-webrtc?api=realtime),
195
+ [client secrets](https://developers.openai.com/api/reference/resources/realtime/subresources/client_secrets/methods/create),
196
+ [conversations/tools](https://developers.openai.com/api/docs/guides/realtime-conversations),
197
+ [image generation](https://developers.openai.com/api/docs/guides/image-generation).
198
+ Compared with [Vercel realtime](https://ai-sdk.dev/docs/ai-sdk-core/realtime) and
199
+ [image generation](https://ai-sdk.dev/docs/ai-sdk-core/image-generation), including
200
+ source and changelog snapshot `fce9210c6405c0ad8452fee3f50717e8ea910b97` (2026-10-16).
201
+ See the repository's media implementation and validation records for exact validation results.
@@ -1,5 +1,12 @@
1
1
  # Image tool results and recorded audio
2
2
 
3
+ Tools can return images to supported models, and audio helpers provide speech generation and recorded voice input.
4
+ For the navigable documentation, start with the [versions and compatibility](https://copilot-sdk.yourgpt.ai/docs/llm-sdk/beta-upgrade),
5
+ [image tool results](https://copilot-sdk.yourgpt.ai/docs/tools/image-results),
6
+ [speech](https://copilot-sdk.yourgpt.ai/docs/llm-sdk/speech),
7
+ [transcription](https://copilot-sdk.yourgpt.ai/docs/llm-sdk/transcription), and
8
+ [recorded voice input](https://copilot-sdk.yourgpt.ai/docs/chat/voice-input).
9
+
3
10
  These APIs are additive. Chat configuration, ordinary JSON/string tool results, and the existing screenshot `addAsUserMessage` attachment path retain their behavior. Native image tool output requires OpenAI **Responses** or Anthropic. TTS and recorded-file STT currently have an OpenAI implementation; other providers can implement the separate `SpeechModel` / `TranscriptionModel` contracts.
4
11
 
5
12
  ## Tools that return images
@@ -50,7 +57,7 @@ OpenAI Chat Completions, Gemini, Ollama, Azure Chat and other compatible endpoin
50
57
 
51
58
  Native output is carried as `modelOutput` next to the tool message's text. Preserve it when storing/restoring messages, along with `tool_call_id` / `toolCallId` and assistant tool calls. The Runtime storage helper places it at `StorageMessage.metadata.modelOutput`; a custom loader must map it back to `modelOutput`. Copilot's serialized `UIMessage` history preserves it directly, including server results in streaming execution metadata. Do not truncate image base64 or substitute it with text. Context pruning can remove complete older tool exchanges.
52
59
 
53
- If an existing screenshot handler explicitly uses `addAsUserMessage`, it continues to add an attachment. When native model output is also supplied, the native output takes precedence and no extra attachment turn is created. General image generation is separate and is not implemented here.
60
+ If an existing screenshot handler explicitly uses `addAsUserMessage`, it continues to add an attachment. When native model output is also supplied, the native output takes precedence and no extra attachment turn is created. General image generation and editing use the API in the [media guide](./media.md).
54
61
 
55
62
  ## Speech generation on the server
56
63
 
@@ -163,7 +170,7 @@ upload?.abort();
163
170
  recorder.dispose();
164
171
  ```
165
172
 
166
- The recorder negotiates supported WebM/Opus, MP4, or Ogg/Opus containers. Stop, cancel, permission races, recorder errors and disposal release microphone tracks. Defaults: 20 MB and 60 seconds; exceeding either discards the recording and invokes `onError`. No automatic message submission, voice cloning, telephony, live transcription, or bidirectional voice call is included.
173
+ The recorder negotiates supported WebM/Opus, MP4, or Ogg/Opus containers. Stop, cancel, permission races, recorder errors and disposal release microphone tracks. Defaults: 20 MB and 60 seconds; exceeding either discards the recording and invokes `onError`. The recorder does not submit chat messages automatically or provide voice cloning, telephony, live transcription or a bidirectional call. For the WebRTC call API, see the [realtime guide](./media.md#realtime-voice-server-setup).
167
174
 
168
175
  ## Verification sources
169
176
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yourgpt/llm-sdk",
3
- "version": "2.6.0-beta.2",
3
+ "version": "2.6.0",
4
4
  "description": "AI SDK for building AI Agents with any LLM",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -135,6 +135,26 @@
135
135
  "types": "./dist/fallback/index.d.ts",
136
136
  "default": "./dist/fallback/index.js"
137
137
  }
138
+ },
139
+ "./typesafe": {
140
+ "import": {
141
+ "types": "./dist/providers/typesafe/index.d.mts",
142
+ "default": "./dist/providers/typesafe/index.mjs"
143
+ },
144
+ "require": {
145
+ "types": "./dist/providers/typesafe/index.d.ts",
146
+ "default": "./dist/providers/typesafe/index.js"
147
+ }
148
+ },
149
+ "./cloudflare": {
150
+ "import": {
151
+ "types": "./dist/providers/cloudflare/index.d.mts",
152
+ "default": "./dist/providers/cloudflare/index.mjs"
153
+ },
154
+ "require": {
155
+ "types": "./dist/providers/cloudflare/index.d.ts",
156
+ "default": "./dist/providers/cloudflare/index.js"
157
+ }
138
158
  }
139
159
  },
140
160
  "homepage": "https://copilot-sdk.yourgpt.ai",
@@ -142,17 +162,10 @@
142
162
  "dist",
143
163
  "!dist/**/*.map",
144
164
  "README.md",
145
- "docs/multimodal-and-audio.md"
165
+ "docs/multimodal-and-audio.md",
166
+ "docs/decisions.md",
167
+ "docs/media.md"
146
168
  ],
147
- "scripts": {
148
- "build": "tsup",
149
- "dev": "tsup --watch",
150
- "clean": "rm -rf dist",
151
- "lint": "eslint src/",
152
- "typecheck": "tsc --noEmit",
153
- "test": "pnpm build && pnpm test:types && node --test test/*.test.cjs",
154
- "test:types": "tsc --noEmit --strict --skipLibCheck --moduleResolution node16 --module Node16 --target ES2020 test/public-types.ts test/public-types.mts"
155
- },
156
169
  "dependencies": {
157
170
  "hono": "^4.3.0",
158
171
  "zod": "^3.23.0"
@@ -207,5 +220,14 @@
207
220
  "sideEffects": false,
208
221
  "engines": {
209
222
  "node": ">=18"
223
+ },
224
+ "scripts": {
225
+ "build": "tsup",
226
+ "dev": "tsup --watch",
227
+ "clean": "rm -rf dist",
228
+ "lint": "eslint src/",
229
+ "typecheck": "tsc --noEmit",
230
+ "test": "pnpm build && pnpm test:types && node --test test/*.test.cjs",
231
+ "test:types": "tsc --noEmit --strict --skipLibCheck --moduleResolution node16 --module Node16 --target ES2020 test/public-types.ts test/public-types.mts"
210
232
  }
211
- }
233
+ }
@@ -1,92 +0,0 @@
1
- /** Audio models are independent of chat models and chat streaming events. */
2
- type SpeechFormat = "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm";
3
- interface AudioResponseMetadata {
4
- provider: string;
5
- modelId: string;
6
- requestId?: string;
7
- status?: number;
8
- }
9
- interface AudioWarning {
10
- code: string;
11
- message: string;
12
- }
13
- interface SpeechOptions {
14
- text: string;
15
- voice?: string;
16
- outputFormat?: SpeechFormat;
17
- speed?: number;
18
- instructions?: string;
19
- signal?: AbortSignal;
20
- providerOptions?: Record<string, Record<string, unknown>>;
21
- }
22
- interface GeneratedSpeech {
23
- audio: {
24
- uint8Array: Uint8Array;
25
- readonly base64: string;
26
- mediaType: string;
27
- format: SpeechFormat;
28
- };
29
- response: AudioResponseMetadata;
30
- warnings: AudioWarning[];
31
- usage?: Record<string, unknown>;
32
- }
33
- interface SpeechModel {
34
- readonly provider: string;
35
- readonly modelId: string;
36
- doGenerate(options: SpeechOptions): Promise<GeneratedSpeech>;
37
- }
38
- interface TranscriptionOptions {
39
- /** Recorded file bytes, base64, or a Blob. URLs and raw PCM are not fetched/decoded. */
40
- audio: Uint8Array | ArrayBuffer | Blob | string;
41
- mediaType?: string;
42
- filename?: string;
43
- language?: string;
44
- prompt?: string;
45
- signal?: AbortSignal;
46
- providerOptions?: Record<string, Record<string, unknown>>;
47
- }
48
- interface TranscriptSegment {
49
- text: string;
50
- start: number;
51
- end: number;
52
- speaker?: string;
53
- }
54
- interface TranscriptionResult {
55
- text: string;
56
- language?: string;
57
- durationSeconds?: number;
58
- segments?: TranscriptSegment[];
59
- words?: Array<{
60
- word: string;
61
- start: number;
62
- end: number;
63
- }>;
64
- response: AudioResponseMetadata;
65
- warnings: AudioWarning[];
66
- usage?: Record<string, unknown>;
67
- /** Provider-supplied extras, including log probabilities when requested. */
68
- providerMetadata?: Record<string, unknown>;
69
- }
70
- interface TranscriptionModel {
71
- readonly provider: string;
72
- readonly modelId: string;
73
- doTranscribe(options: TranscriptionOptions): Promise<TranscriptionResult>;
74
- }
75
- declare const audioErrorMarker: unique symbol;
76
- declare class AudioAPIError extends Error {
77
- readonly code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error";
78
- readonly status?: number | undefined;
79
- readonly requestId?: string | undefined;
80
- readonly [audioErrorMarker] = true;
81
- static [Symbol.hasInstance](value: unknown): boolean;
82
- readonly name = "AudioAPIError";
83
- constructor(message: string, code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error", status?: number | undefined, requestId?: string | undefined);
84
- }
85
- declare function generateSpeech({ model, ...options }: SpeechOptions & {
86
- model: SpeechModel;
87
- }): Promise<GeneratedSpeech>;
88
- declare function transcribe({ model, ...options }: TranscriptionOptions & {
89
- model: TranscriptionModel;
90
- }): Promise<TranscriptionResult>;
91
-
92
- export { AudioAPIError as A, type GeneratedSpeech as G, type SpeechModel as S, type TranscriptionModel as T, type SpeechOptions as a, type TranscriptionOptions as b, type TranscriptionResult as c, type SpeechFormat as d, type TranscriptSegment as e, type AudioResponseMetadata as f, generateSpeech as g, type AudioWarning as h, transcribe as t };
@@ -1,92 +0,0 @@
1
- /** Audio models are independent of chat models and chat streaming events. */
2
- type SpeechFormat = "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm";
3
- interface AudioResponseMetadata {
4
- provider: string;
5
- modelId: string;
6
- requestId?: string;
7
- status?: number;
8
- }
9
- interface AudioWarning {
10
- code: string;
11
- message: string;
12
- }
13
- interface SpeechOptions {
14
- text: string;
15
- voice?: string;
16
- outputFormat?: SpeechFormat;
17
- speed?: number;
18
- instructions?: string;
19
- signal?: AbortSignal;
20
- providerOptions?: Record<string, Record<string, unknown>>;
21
- }
22
- interface GeneratedSpeech {
23
- audio: {
24
- uint8Array: Uint8Array;
25
- readonly base64: string;
26
- mediaType: string;
27
- format: SpeechFormat;
28
- };
29
- response: AudioResponseMetadata;
30
- warnings: AudioWarning[];
31
- usage?: Record<string, unknown>;
32
- }
33
- interface SpeechModel {
34
- readonly provider: string;
35
- readonly modelId: string;
36
- doGenerate(options: SpeechOptions): Promise<GeneratedSpeech>;
37
- }
38
- interface TranscriptionOptions {
39
- /** Recorded file bytes, base64, or a Blob. URLs and raw PCM are not fetched/decoded. */
40
- audio: Uint8Array | ArrayBuffer | Blob | string;
41
- mediaType?: string;
42
- filename?: string;
43
- language?: string;
44
- prompt?: string;
45
- signal?: AbortSignal;
46
- providerOptions?: Record<string, Record<string, unknown>>;
47
- }
48
- interface TranscriptSegment {
49
- text: string;
50
- start: number;
51
- end: number;
52
- speaker?: string;
53
- }
54
- interface TranscriptionResult {
55
- text: string;
56
- language?: string;
57
- durationSeconds?: number;
58
- segments?: TranscriptSegment[];
59
- words?: Array<{
60
- word: string;
61
- start: number;
62
- end: number;
63
- }>;
64
- response: AudioResponseMetadata;
65
- warnings: AudioWarning[];
66
- usage?: Record<string, unknown>;
67
- /** Provider-supplied extras, including log probabilities when requested. */
68
- providerMetadata?: Record<string, unknown>;
69
- }
70
- interface TranscriptionModel {
71
- readonly provider: string;
72
- readonly modelId: string;
73
- doTranscribe(options: TranscriptionOptions): Promise<TranscriptionResult>;
74
- }
75
- declare const audioErrorMarker: unique symbol;
76
- declare class AudioAPIError extends Error {
77
- readonly code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error";
78
- readonly status?: number | undefined;
79
- readonly requestId?: string | undefined;
80
- readonly [audioErrorMarker] = true;
81
- static [Symbol.hasInstance](value: unknown): boolean;
82
- readonly name = "AudioAPIError";
83
- constructor(message: string, code: "invalid_input" | "unsupported_option" | "provider_error" | "invalid_response" | "network_error", status?: number | undefined, requestId?: string | undefined);
84
- }
85
- declare function generateSpeech({ model, ...options }: SpeechOptions & {
86
- model: SpeechModel;
87
- }): Promise<GeneratedSpeech>;
88
- declare function transcribe({ model, ...options }: TranscriptionOptions & {
89
- model: TranscriptionModel;
90
- }): Promise<TranscriptionResult>;
91
-
92
- export { AudioAPIError as A, type GeneratedSpeech as G, type SpeechModel as S, type TranscriptionModel as T, type SpeechOptions as a, type TranscriptionOptions as b, type TranscriptionResult as c, type SpeechFormat as d, type TranscriptSegment as e, type AudioResponseMetadata as f, generateSpeech as g, type AudioWarning as h, transcribe as t };