@opencode/ai 0.0.0-dev-20175 → 0.0.0-dev-20180
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
CHANGED
|
@@ -699,7 +699,7 @@ const program = Effect.gen(function* () {
|
|
|
699
699
|
})
|
|
700
700
|
```
|
|
701
701
|
|
|
702
|
-
The hosted result is represented as a provider-executed tool call and tool result
|
|
702
|
+
The hosted result is represented as a provider-executed tool call and a tool result whose content carries the generated image as a file. Gemini image-capable models instead emit a first-class `media` `LLMEvent` for inline image output (`response.message` then carries a `media` part). Retaining `response.message` preserves the generated image for continuation on both routes.
|
|
703
703
|
|
|
704
704
|
## Video generation
|
|
705
705
|
|
|
@@ -840,9 +840,10 @@ Provider notes:
|
|
|
840
840
|
- **OpenAI** streams over SSE (`stream_format: "sse"`), which is also the only place it reports token usage; `tts-1`
|
|
841
841
|
and `tts-1-hd` do not support SSE and stream the raw audio body instead. `pcm` is 24 kHz 16-bit mono. `language`
|
|
842
842
|
and `timestamps` are not supported.
|
|
843
|
-
- **Gemini TTS** returns
|
|
844
|
-
|
|
845
|
-
|
|
843
|
+
- **Gemini TTS** returns the provider's default output: WAV for Gemini 3.8 TTS `generate`, raw 16-bit PCM
|
|
844
|
+
(`audio/L16;codec=pcm;rate=24000`) otherwise. `pcm` is the only explicit `format` it accepts, and it fails typed on
|
|
845
|
+
Gemini 3.8 `generate`; the route never wraps PCM as WAV. Style is directed in the text, so `instructions` and
|
|
846
|
+
`speed` fail typed. Only `gemini-3.1-flash-tts-preview` and later support streaming. Two-speaker audio goes through
|
|
846
847
|
`providerOptions.speechConfig.multiSpeakerVoiceConfig`.
|
|
847
848
|
- **ElevenLabs** requires `voice` (the path voice id) and authenticates with `xi-api-key`. `format` maps to the
|
|
848
849
|
`output_format` query parameter (`mp3_44100_128`, `pcm_24000`, `wav_24000`, `opus_48000_64`);
|
|
@@ -944,6 +945,7 @@ const transcript = await generation.await({ poll: { interval: 3_000 } })
|
|
|
944
945
|
- **`ImageClient`** — Effect service and layer for image execution, parallel to `LLMClient`.
|
|
945
946
|
- **`Media`** — the shared asset type (`Media.Asset`, `Media.Source`) and constructors used by messages, tool results, and media requests.
|
|
946
947
|
- **`Generation`** — provider-neutral handle for an in-flight media generation (`await`, `refresh`, `cancel`, `events`) used by queued media routes.
|
|
948
|
+
- **`Video.request` / `generate` / `stream` / `start` / `resume`** — queued video generation through a provider-neutral request; `VideoClient` is its Effect service and layer.
|
|
947
949
|
- **`Speech.request` / `Speech.generate` / `Speech.stream`** — text-to-speech through a provider-neutral request; `SpeechClient` is its Effect service and layer.
|
|
948
950
|
- **`Transcription.request` / `generate` / `stream` / `start` / `resume`** — speech-to-text over inline, streaming, and queued routes; `TranscriptionClient` is its Effect service and layer.
|
|
949
951
|
- **`AIClient.layer` / `AIClient.layerWith(executor)`** — every modality client plus the request executor in one layer.
|
package/dist/generation.d.ts
CHANGED
|
@@ -65,9 +65,10 @@ export declare class Generation<Response> {
|
|
|
65
65
|
await(options?: AwaitOptions): Effect.Effect<Response, AIError>;
|
|
66
66
|
cancel(): Effect.Effect<void, AIError>;
|
|
67
67
|
/**
|
|
68
|
-
* Status observations as a stream, ending after the first terminal observation. Each poll
|
|
69
|
-
* remaining until `poll.timeout`, so a hung status request
|
|
70
|
-
*
|
|
68
|
+
* Status observations as a stream, ending after the first terminal observation. Each poll and each sleep between polls
|
|
69
|
+
* is bounded by the time remaining until `poll.timeout`, so a hung status request or a long interval fails the stream at
|
|
70
|
+
* the deadline instead of stalling it. (`Stream.interruptWhen` would express this directly but deadlocks under
|
|
71
|
+
* `TestClock` when the source completes while the timer sleeps.)
|
|
71
72
|
*/
|
|
72
73
|
events(options?: AwaitOptions): Stream.Stream<Event, AIError>;
|
|
73
74
|
private event;
|
package/dist/generation.js
CHANGED
|
@@ -62,9 +62,10 @@ export class Generation {
|
|
|
62
62
|
return this.route.cancel ?? Effect.void;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
65
|
-
* Status observations as a stream, ending after the first terminal observation. Each poll
|
|
66
|
-
* remaining until `poll.timeout`, so a hung status request
|
|
67
|
-
*
|
|
65
|
+
* Status observations as a stream, ending after the first terminal observation. Each poll and each sleep between polls
|
|
66
|
+
* is bounded by the time remaining until `poll.timeout`, so a hung status request or a long interval fails the stream at
|
|
67
|
+
* the deadline instead of stalling it. (`Stream.interruptWhen` would express this directly but deadlocks under
|
|
68
|
+
* `TestClock` when the source completes while the timer sleeps.)
|
|
68
69
|
*/
|
|
69
70
|
events(options) {
|
|
70
71
|
if (this.terminal)
|
|
@@ -72,11 +73,16 @@ export class Generation {
|
|
|
72
73
|
const timeout = Duration.fromInputUnsafe(options?.poll?.timeout ?? DEFAULT_POLL_TIMEOUT);
|
|
73
74
|
return Stream.unwrap(Clock.currentTimeMillis.pipe(Effect.map((start) => {
|
|
74
75
|
const deadline = start + Duration.toMillis(timeout);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
// Fail before polling once the deadline has passed: a fast status request could otherwise win the zero-budget
|
|
77
|
+
// race and schedule another zero-delay poll.
|
|
78
|
+
const refresh = Clock.currentTimeMillis.pipe(Effect.flatMap((now) => now >= deadline
|
|
79
|
+
? this.timeoutError(timeout)
|
|
80
|
+
: this.refresh().pipe(Effect.timeoutOrElse({
|
|
81
|
+
duration: Duration.millis(deadline - now),
|
|
82
|
+
orElse: () => this.timeoutError(timeout),
|
|
83
|
+
}))));
|
|
84
|
+
const schedule = this.schedule(options?.poll).pipe(Schedule.modifyDelay((meta) => Effect.succeed(Duration.min(meta.duration, Duration.millis(Math.max(0, deadline - meta.now))))));
|
|
85
|
+
return Stream.fromEffectSchedule(refresh, schedule).pipe(Stream.takeUntil((generation) => generation.terminal), Stream.map((generation) => generation.event()));
|
|
80
86
|
})));
|
|
81
87
|
}
|
|
82
88
|
event() {
|
|
@@ -38,6 +38,9 @@ const queryParameters = (request) => {
|
|
|
38
38
|
});
|
|
39
39
|
};
|
|
40
40
|
const fromRequest = Effect.fn("DeepgramSpeech.fromRequest")(function* (request) {
|
|
41
|
+
// Not in `unsupported`: that list would also reject `timestamps: false`, which asks for nothing.
|
|
42
|
+
if (request.timestamps === true)
|
|
43
|
+
return yield* route.unsupported("media.timestamps", `${route.name} does not return timestamps`);
|
|
41
44
|
if (request.format !== undefined &&
|
|
42
45
|
FORMATS[request.format] === undefined &&
|
|
43
46
|
request.providerOptions?.encoding === undefined)
|
|
@@ -74,7 +77,7 @@ const finish = (state, context) => {
|
|
|
74
77
|
// 7. Protocol and route
|
|
75
78
|
// ---------------------------------------------------------------------------
|
|
76
79
|
export const protocol = MediaProtocol.stream(route, {
|
|
77
|
-
unsupported: ["voice", "language", "instructions"
|
|
80
|
+
unsupported: ["voice", "language", "instructions"],
|
|
78
81
|
body: { from: fromRequest },
|
|
79
82
|
frames: (bytes) => bytes,
|
|
80
83
|
initial: () => ({ chunks: [] }),
|
|
@@ -20,6 +20,9 @@ const decodeChunk = route.decodeFrame(GenerateContentChunk);
|
|
|
20
20
|
// 5. Request body construction
|
|
21
21
|
// ---------------------------------------------------------------------------
|
|
22
22
|
const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request) {
|
|
23
|
+
// Not in `unsupported`: that list would also reject `timestamps: false`, which asks for nothing.
|
|
24
|
+
if (request.timestamps === true)
|
|
25
|
+
return yield* route.unsupported("media.timestamps", `${route.name} does not return timestamps`);
|
|
23
26
|
if (request.format === "pcm" && request.mode === "generate" && /^gemini-3\.8-.*-tts(?:-|$)/.test(request.model.id))
|
|
24
27
|
return yield* route.unsupported("media.format", `${route.name} returns WAV by default for Gemini 3.8 TTS unary requests; omit the format to accept it`);
|
|
25
28
|
if (request.format !== undefined && request.format !== "pcm")
|
|
@@ -66,7 +69,7 @@ const finish = (state, context) => {
|
|
|
66
69
|
// 7. Protocol and route
|
|
67
70
|
// ---------------------------------------------------------------------------
|
|
68
71
|
export const protocol = MediaProtocol.stream(route, {
|
|
69
|
-
unsupported: ["instructions", "speed"
|
|
72
|
+
unsupported: ["instructions", "speed"],
|
|
70
73
|
body: { from: fromRequest },
|
|
71
74
|
frames: (bytes, context) => GeminiGenerateContent.frames(bytes, context.request.mode),
|
|
72
75
|
initial: () => ({ chunks: [] }),
|
|
@@ -31,6 +31,9 @@ const decodeEvent = route.decodeFrame(SpeechStreamEvent);
|
|
|
31
31
|
// `sse` is not supported for `tts-1` or `tts-1-hd`; those models stream the raw audio body instead.
|
|
32
32
|
const supportsSse = (model) => !/^tts-1(-hd)?(-|$)/.test(model);
|
|
33
33
|
const fromRequest = Effect.fn("OpenAISpeech.fromRequest")(function* (request) {
|
|
34
|
+
// Not in `unsupported`: that list would also reject `timestamps: false`, which asks for nothing.
|
|
35
|
+
if (request.timestamps === true)
|
|
36
|
+
return yield* route.unsupported("media.timestamps", `${route.name} does not return timestamps`);
|
|
34
37
|
return MediaProtocol.json(mergeJsonRecords({
|
|
35
38
|
model: request.model.id,
|
|
36
39
|
input: request.text,
|
|
@@ -80,7 +83,7 @@ const finish = (state, context) => {
|
|
|
80
83
|
// 7. Protocol and route
|
|
81
84
|
// ---------------------------------------------------------------------------
|
|
82
85
|
export const protocol = MediaProtocol.stream(route, {
|
|
83
|
-
unsupported: ["language"
|
|
86
|
+
unsupported: ["language"],
|
|
84
87
|
body: { from: fromRequest },
|
|
85
88
|
frames: (bytes, context) => (isSse(context.body) ? Framing.sse.frame(bytes) : bytes),
|
|
86
89
|
initial: () => ({ chunks: [], done: false }),
|
package/dist/route/media.js
CHANGED
|
@@ -196,7 +196,11 @@ const encode = (body, headers) => {
|
|
|
196
196
|
apply: HttpClientRequest.bodyFormData(body.value),
|
|
197
197
|
};
|
|
198
198
|
};
|
|
199
|
-
/**
|
|
199
|
+
/**
|
|
200
|
+
* Common fields are never silently dropped: a present field the protocol declared unsupported fails typed. `false`
|
|
201
|
+
* counts as present because some booleans mean something when false (video `audio`); protocols reject opt-in
|
|
202
|
+
* booleans such as speech `timestamps` with `=== true` in `body.from` instead of listing them.
|
|
203
|
+
*/
|
|
200
204
|
const rejectUnsupported = (route, provider, request, unsupported) => {
|
|
201
205
|
const present = (unsupported ?? []).filter((field) => {
|
|
202
206
|
const value = request[field];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20180",
|
|
4
4
|
"name": "@opencode/ai",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@clack/prompts": "1.0.0-alpha.1",
|
|
36
36
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
37
|
-
"@opencode/http-recorder": "0.0.0-dev-
|
|
37
|
+
"@opencode/http-recorder": "0.0.0-dev-20180",
|
|
38
38
|
"@tsconfig/bun": "1.0.9",
|
|
39
39
|
"@types/bun": "1.4.0",
|
|
40
40
|
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@aws-sdk/credential-providers": "3.1057.0",
|
|
45
45
|
"@smithy/eventstream-codec": "4.2.14",
|
|
46
46
|
"@smithy/util-utf8": "4.2.2",
|
|
47
|
-
"@opencode/schema": "0.0.0-dev-
|
|
47
|
+
"@opencode/schema": "0.0.0-dev-20180",
|
|
48
48
|
"aws4fetch": "1.0.20",
|
|
49
49
|
"effect": "4.0.0-rc.112",
|
|
50
50
|
"google-auth-library": "10.5.0"
|