@opencode/ai 0.0.0-dev-20208 → 0.0.0-dev-20210

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.
@@ -435,16 +435,7 @@ const mapFinishReason = (finishReason, hasToolCalls) => {
435
435
  return hasToolCalls ? "tool-calls" : "stop";
436
436
  if (finishReason === "MAX_TOKENS")
437
437
  return "length";
438
- if (finishReason === "IMAGE_SAFETY" ||
439
- finishReason === "RECITATION" ||
440
- finishReason === "SAFETY" ||
441
- finishReason === "BLOCKLIST" ||
442
- finishReason === "PROHIBITED_CONTENT" ||
443
- finishReason === "SPII" ||
444
- finishReason === "MODEL_ARMOR" ||
445
- finishReason === "IMAGE_PROHIBITED_CONTENT" ||
446
- finishReason === "IMAGE_RECITATION" ||
447
- finishReason === "LANGUAGE")
438
+ if (GeminiGenerateContent.contentFiltered(finishReason))
448
439
  return "content-filter";
449
440
  if (finishReason === "MALFORMED_FUNCTION_CALL" ||
450
441
  finishReason === "UNEXPECTED_TOOL_CALL" ||
@@ -49,9 +49,15 @@ const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
49
49
  return yield* blocked;
50
50
  const audio = (chunk.candidates?.[0]?.content?.parts ?? []).flatMap((part) => part.inlineData === undefined ? [] : [part.inlineData]);
51
51
  const next = { ...GeminiGenerateContent.track(state, chunk), mimeType: state.mimeType ?? audio[0]?.mimeType };
52
- return [next, audio.flatMap((part) => SpeechStream.delta(next, part.data)[1])];
52
+ const events = audio.flatMap((part) => SpeechStream.delta(next, part.data)[1]);
53
+ const withheld = next.chunks.length === 0 ? GeminiGenerateContent.withheld(route.name, chunk, frame) : undefined;
54
+ if (withheld !== undefined)
55
+ return yield* withheld;
56
+ return [next, events];
53
57
  });
54
58
  const finish = (state, context) => {
59
+ if (state.finishReason === undefined)
60
+ return Effect.fail(route.incomplete());
55
61
  const sampleRate = SpeechStream.sampleRate(state.mimeType) ?? DEFAULT_SAMPLE_RATE;
56
62
  const output = state.mimeType?.split(";")[0]?.toLowerCase() === "audio/wav"
57
63
  ? SpeechStream.container("wav", sampleRate)
@@ -61,8 +67,9 @@ const finish = (state, context) => {
61
67
  return SpeechStream.finish(route, state, {
62
68
  ...output,
63
69
  usage: GeminiGenerateContent.usage(state.usage),
70
+ notices: GeminiGenerateContent.notices(route.name, state),
64
71
  providerMetadata: GeminiGenerateContent.providerMetadata(state),
65
- detail: state.finishReason === undefined ? undefined : `finish reason: ${state.finishReason}`,
72
+ detail: `finish reason: ${state.finishReason}`,
66
73
  });
67
74
  };
68
75
  // ---------------------------------------------------------------------------
@@ -85,6 +85,9 @@ const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
85
85
  .filter((item) => item.length > 0)
86
86
  .join(" ");
87
87
  const delta = text.length === 0 || state.text.length === 0 ? text : ` ${text}`;
88
+ const withheld = state.text.length + delta.length === 0 ? GeminiGenerateContent.withheld(route.name, chunk, frame) : undefined;
89
+ if (withheld !== undefined)
90
+ return yield* withheld;
88
91
  const events = [
89
92
  ...(delta.length === 0 ? [] : [TranscriptionTextDeltaEvent.make({ delta })]),
90
93
  ...segments.map((segment) => TranscriptionSegmentEvent.make({ segment })),
@@ -100,6 +103,7 @@ const finish = (state) => {
100
103
  segments: state.segments.length === 0 ? undefined : state.segments,
101
104
  words: state.words.length === 0 ? undefined : state.words,
102
105
  usage: GeminiGenerateContent.usage(state.usage),
106
+ notices: GeminiGenerateContent.notices(route.name, state),
103
107
  providerMetadata: GeminiGenerateContent.providerMetadata(state),
104
108
  }),
105
109
  ]);
@@ -47,6 +47,12 @@ export interface Metadata {
47
47
  }
48
48
  export declare const track: <State extends Metadata>(state: State, chunk: Chunk) => State;
49
49
  export declare const blocked: (name: string, chunk: Chunk, frame: string) => AIError | undefined;
50
+ /** Finish reasons for which Gemini stops output on safety or policy grounds. */
51
+ export declare const contentFiltered: (finishReason: string | undefined) => boolean;
52
+ /** Callers check that the response produced no output: a policy stop after output is a partial result instead. */
53
+ export declare const withheld: (name: string, chunk: Chunk, frame: string) => AIError | undefined;
54
+ /** Any finish reason other than `STOP` means the output may be cut short, so it is surfaced rather than dropped. */
55
+ export declare const notices: (name: string, state: Metadata) => ReadonlyArray<Media.Notice> | undefined;
50
56
  export declare const usage: (usage: UsageMetadata | undefined) => MediaUsage | undefined;
51
57
  export declare const providerMetadata: (state: Metadata) => ProviderMetadata;
52
58
  export declare const path: (model: string, mode: MediaProtocol.Mode) => string;
@@ -40,6 +40,39 @@ export const blocked = (name, chunk, frame) => {
40
40
  }),
41
41
  });
42
42
  };
43
+ const CONTENT_FILTER_REASONS = new Set([
44
+ "IMAGE_SAFETY",
45
+ "RECITATION",
46
+ "SAFETY",
47
+ "BLOCKLIST",
48
+ "PROHIBITED_CONTENT",
49
+ "SPII",
50
+ "MODEL_ARMOR",
51
+ "IMAGE_PROHIBITED_CONTENT",
52
+ "IMAGE_RECITATION",
53
+ "LANGUAGE",
54
+ ]);
55
+ /** Finish reasons for which Gemini stops output on safety or policy grounds. */
56
+ export const contentFiltered = (finishReason) => finishReason !== undefined && CONTENT_FILTER_REASONS.has(finishReason);
57
+ /** Callers check that the response produced no output: a policy stop after output is a partial result instead. */
58
+ export const withheld = (name, chunk, frame) => {
59
+ const finishReason = chunk.candidates?.[0]?.finishReason;
60
+ if (!contentFiltered(finishReason))
61
+ return undefined;
62
+ return new AIError({
63
+ reason: new ContentPolicyError({ message: `${name} withheld its output (${finishReason})`, body: frame }),
64
+ });
65
+ };
66
+ /** Any finish reason other than `STOP` means the output may be cut short, so it is surfaced rather than dropped. */
67
+ export const notices = (name, state) => state.finishReason === undefined || state.finishReason === "STOP"
68
+ ? undefined
69
+ : [
70
+ {
71
+ type: contentFiltered(state.finishReason) ? "filtered" : "other",
72
+ message: `${name} finished with ${state.finishReason}`,
73
+ providerMetadata: { google: { finishReason: state.finishReason } },
74
+ },
75
+ ];
43
76
  export const usage = (usage) => usage === undefined
44
77
  ? undefined
45
78
  : {
@@ -42,6 +42,7 @@ export declare const finish: (route: MediaProtocol.Identity, state: Audio, outpu
42
42
  readonly mediaType: string | undefined;
43
43
  readonly info?: Media.Info;
44
44
  readonly usage?: MediaUsage;
45
+ readonly notices?: ReadonlyArray<Media.Notice>;
45
46
  readonly providerMetadata?: ProviderMetadata;
46
47
  readonly detail?: string;
47
48
  }) => Effect.Effect<ReadonlyArray<SpeechEvent>, AIError>;
@@ -51,6 +51,7 @@ export const finish = (route, state, output) => {
51
51
  SpeechFinishEvent.make({
52
52
  audio: Media.bytes(concatBytes(state.chunks), output.mediaType, { info: output.info }),
53
53
  usage: output.usage,
54
+ notices: output.notices,
54
55
  providerMetadata: output.providerMetadata,
55
56
  }),
56
57
  ]);
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-20208",
3
+ "version": "0.0.0-dev-20210",
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-20208",
37
+ "@opencode/http-recorder": "0.0.0-dev-20210",
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-20208",
47
+ "@opencode/schema": "0.0.0-dev-20210",
48
48
  "aws4fetch": "1.0.20",
49
49
  "effect": "4.0.0-rc.112",
50
50
  "google-auth-library": "10.5.0"