@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.
- package/LICENSE +21 -0
- package/README.md +87 -17
- package/dist/adapters/index.d.mts +11 -13
- package/dist/adapters/index.d.ts +11 -13
- package/dist/adapters/index.js +406 -114
- package/dist/adapters/index.mjs +406 -114
- package/dist/audio-K-Z3-Bce.d.mts +191 -0
- package/dist/audio-K-Z3-Bce.d.ts +191 -0
- package/dist/{base-BICaQaRS.d.mts → base-6LzczuCg.d.ts} +8 -1
- package/dist/{base-CpRPB8u8.d.ts → base-DySbTP8C.d.mts} +8 -1
- package/dist/decisions-CJzOt87t.d.mts +137 -0
- package/dist/decisions-CJzOt87t.d.ts +137 -0
- package/dist/fallback/index.d.mts +4 -4
- package/dist/fallback/index.d.ts +4 -4
- package/dist/http-BQxUT5A9.d.mts +9 -0
- package/dist/http-BQxUT5A9.d.ts +9 -0
- package/dist/index.d.mts +17 -9
- package/dist/index.d.ts +17 -9
- package/dist/index.js +698 -205
- package/dist/index.mjs +695 -206
- package/dist/providers/anthropic/index.d.mts +7 -4
- package/dist/providers/anthropic/index.d.ts +7 -4
- package/dist/providers/anthropic/index.js +389 -115
- package/dist/providers/anthropic/index.mjs +389 -115
- package/dist/providers/azure/index.d.mts +3 -3
- package/dist/providers/azure/index.d.ts +3 -3
- package/dist/providers/cloudflare/index.d.mts +11 -0
- package/dist/providers/cloudflare/index.d.ts +11 -0
- package/dist/providers/cloudflare/index.js +743 -0
- package/dist/providers/cloudflare/index.mjs +741 -0
- package/dist/providers/fireworks/index.d.mts +3 -3
- package/dist/providers/fireworks/index.d.ts +3 -3
- package/dist/providers/fireworks/index.js +181 -60
- package/dist/providers/fireworks/index.mjs +181 -60
- package/dist/providers/google/index.d.mts +3 -3
- package/dist/providers/google/index.d.ts +3 -3
- package/dist/providers/google/index.js +181 -60
- package/dist/providers/google/index.mjs +181 -60
- package/dist/providers/ollama/index.d.mts +4 -4
- package/dist/providers/ollama/index.d.ts +4 -4
- package/dist/providers/openai/index.d.mts +38 -6
- package/dist/providers/openai/index.d.ts +38 -6
- package/dist/providers/openai/index.js +1451 -85
- package/dist/providers/openai/index.mjs +1449 -86
- package/dist/providers/openrouter/index.d.mts +21 -4
- package/dist/providers/openrouter/index.d.ts +21 -4
- package/dist/providers/openrouter/index.js +1040 -131
- package/dist/providers/openrouter/index.mjs +1040 -132
- package/dist/providers/togetherai/index.d.mts +3 -3
- package/dist/providers/togetherai/index.d.ts +3 -3
- package/dist/providers/togetherai/index.js +181 -60
- package/dist/providers/togetherai/index.mjs +181 -60
- package/dist/providers/typesafe/index.d.mts +8 -0
- package/dist/providers/typesafe/index.d.ts +8 -0
- package/dist/providers/typesafe/index.js +735 -0
- package/dist/providers/typesafe/index.mjs +733 -0
- package/dist/providers/xai/index.d.mts +3 -3
- package/dist/providers/xai/index.d.ts +3 -3
- package/dist/providers/xai/index.js +181 -60
- package/dist/providers/xai/index.mjs +181 -60
- package/dist/{types-DCFqkJAy.d.ts → types-B0y6VZSQ.d.ts} +1 -1
- package/dist/{types-DHQEMFHA.d.mts → types-BXQMtaCX.d.ts} +17 -3
- package/dist/{types-C3RgRbEC.d.mts → types-BY6BVhFg.d.mts} +1 -1
- package/dist/{types-Dikj3En4.d.ts → types-C7_qVSSd.d.mts} +17 -3
- package/dist/{types-Dg1Bcg1k.d.mts → types-D7lzMMLu.d.mts} +13 -1
- package/dist/{types-Dg1Bcg1k.d.ts → types-D7lzMMLu.d.ts} +13 -1
- package/dist/yourgpt/index.d.mts +1 -1
- package/dist/yourgpt/index.d.ts +1 -1
- package/docs/decisions.md +174 -0
- package/docs/media.md +201 -0
- package/docs/multimodal-and-audio.md +9 -2
- package/package.json +34 -12
- package/dist/audio-BBSwnjo8.d.mts +0 -92
- 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
|
|
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`.
|
|
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
|
|
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 };
|
package/dist/audio-BBSwnjo8.d.ts
DELETED
|
@@ -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 };
|