@opencode-ai/ai
Advanced tools
@@ -568,3 +568,3 @@ import { Effect, Schema } from "effect"; | ||
| step, | ||
| onHalt, | ||
| onHalt: (state) => Effect.succeed(onHalt(state)), | ||
| }, | ||
@@ -571,0 +571,0 @@ }); |
@@ -577,3 +577,3 @@ import { Effect, Option, Schema } from "effect"; | ||
| step, | ||
| onHalt: finish, | ||
| onHalt: (state) => Effect.succeed(finish(state)), | ||
| }, | ||
@@ -580,0 +580,0 @@ }); |
@@ -387,2 +387,3 @@ import { Effect, Schema } from "effect"; | ||
| readonly delta: Schema.optional<Schema.String>; | ||
| readonly arguments: Schema.optional<Schema.String>; | ||
| readonly text: Schema.optional<Schema.String>; | ||
@@ -1013,2 +1014,3 @@ readonly item_id: Schema.optional<Schema.String>; | ||
| } | undefined; | ||
| readonly arguments?: string | undefined; | ||
| readonly param?: string | null | undefined; | ||
@@ -1015,0 +1017,0 @@ readonly status_code?: unknown; |
@@ -207,2 +207,3 @@ import { Effect, Schema } from "effect"; | ||
| delta: Schema.optional(Schema.String), | ||
| arguments: Schema.optional(Schema.String), | ||
| text: Schema.optional(Schema.String), | ||
@@ -760,5 +761,19 @@ item_id: Schema.optional(Schema.String), | ||
| const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgumentsDelta")(function* (state, event) { | ||
| if (!event.item_id || !event.delta || !state.tools[event.item_id]) | ||
| if (!event.item_id) | ||
| return [state, NO_EVENTS]; | ||
| const result = ToolStream.appendExisting(state.id, state.tools, event.item_id, event.delta, `${state.name} tool argument delta is missing its tool call`); | ||
| const tool = state.tools[event.item_id]; | ||
| if (!tool) | ||
| return [state, NO_EVENTS]; | ||
| const final = event.type === "response.function_call_arguments.done" ? event.arguments : undefined; | ||
| if (event.type === "response.function_call_arguments.done" && final === undefined) | ||
| return [state, NO_EVENTS]; | ||
| if (final !== undefined && !final.startsWith(tool.input)) | ||
| return [ | ||
| { ...state, tools: ToolStream.start(state.tools, event.item_id, { ...tool, input: final }) }, | ||
| NO_EVENTS, | ||
| ]; | ||
| const delta = final === undefined ? event.delta : final.slice(tool.input.length); | ||
| if (!delta) | ||
| return [state, NO_EVENTS]; | ||
| const result = ToolStream.appendExisting(state.id, state.tools, event.item_id, delta, `${state.name} tool argument delta is missing its tool call`); | ||
| if (ToolStream.isError(result)) | ||
@@ -948,3 +963,3 @@ return yield* result; | ||
| } | ||
| if (event.type === "response.function_call_arguments.delta") | ||
| if (event.type === "response.function_call_arguments.delta" || event.type === "response.function_call_arguments.done") | ||
| return event.item_id | ||
@@ -951,0 +966,0 @@ ? onFunctionCallArgumentsDelta(state, event) |
@@ -207,3 +207,3 @@ import { Effect, Schema } from "effect"; | ||
| export type OpenAIChatBody = Schema.Schema.Type<typeof OpenAIChatBody>; | ||
| export declare const OpenAIChatEvent: Schema.Struct<{ | ||
| export declare const OpenAIChatEvent: Schema.StructWithRest<Schema.Struct<{ | ||
| readonly choices: Schema.optional<Schema.NullOr<Schema.$Array<Schema.StructWithRest<Schema.Struct<{ | ||
@@ -261,7 +261,7 @@ readonly delta: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{ | ||
| }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>; | ||
| readonly error: Schema.optional<Schema.NullOr<Schema.Struct<{ | ||
| readonly error: Schema.optional<Schema.NullOr<Schema.StructWithRest<Schema.Struct<{ | ||
| readonly code: Schema.optional<Schema.NullOr<Schema.Union<readonly [Schema.String, Schema.Number]>>>; | ||
| readonly message: Schema.String; | ||
| }>>>; | ||
| }>; | ||
| }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>>>; | ||
| }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>; | ||
| export type OpenAIChatEvent = Schema.Schema.Type<typeof OpenAIChatEvent>; | ||
@@ -286,2 +286,3 @@ interface PendingToolDelta { | ||
| readonly nextToolIndex: number; | ||
| readonly requireFinishReason: boolean; | ||
| } | ||
@@ -591,3 +592,5 @@ interface LoweringOptions { | ||
| }, string, { | ||
| readonly [x: string]: unknown; | ||
| readonly error?: { | ||
| readonly [x: string]: unknown; | ||
| readonly message: string; | ||
@@ -594,0 +597,0 @@ readonly code?: string | number | null | undefined; |
@@ -8,3 +8,3 @@ import { Effect, Schema } from "effect"; | ||
| import { Protocol } from "../route/protocol.js"; | ||
| import { AIError, LLMEvent, Usage, } from "../schema/index.js"; | ||
| import { AIError, InvalidProviderOutputReason, LLMEvent, ProviderInternalReason, UnknownProviderReason, Usage, } from "../schema/index.js"; | ||
| import { classifyProviderFailure } from "../provider-error.js"; | ||
@@ -171,11 +171,11 @@ import { isRecord, JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"; | ||
| }), [Schema.Record(Schema.String, Schema.Unknown)]); | ||
| const OpenAIChatError = Schema.Struct({ | ||
| const OpenAIChatError = Schema.StructWithRest(Schema.Struct({ | ||
| code: optionalNull(Schema.Union([Schema.String, Schema.Number])), | ||
| message: Schema.String, | ||
| }); | ||
| export const OpenAIChatEvent = Schema.Struct({ | ||
| }), [Schema.Record(Schema.String, Schema.Unknown)]); | ||
| export const OpenAIChatEvent = Schema.StructWithRest(Schema.Struct({ | ||
| choices: optionalNull(Schema.Array(OpenAIChatChoice)), | ||
| usage: optionalNull(OpenAIChatUsage), | ||
| error: optionalNull(OpenAIChatError), | ||
| }); | ||
| }), [Schema.Record(Schema.String, Schema.Unknown)]); | ||
| const lowerTool = (tool, inputSchema, options, supportsStrictMode) => ({ | ||
@@ -582,15 +582,28 @@ type: "function", | ||
| // because OpenAI streams JSON arguments across multiple deltas. | ||
| const mapFinishReason = (reason) => { | ||
| if (reason === "stop") | ||
| return "stop"; | ||
| if (reason === "length") | ||
| return "length"; | ||
| if (reason === "content_filter") | ||
| return "content-filter"; | ||
| if (reason === "function_call" || reason === "tool_calls") | ||
| return "tool-calls"; | ||
| if (reason === "error") | ||
| return "error"; | ||
| return "unknown"; | ||
| }; | ||
| const finishReasonError = (event, reason) => new AIError({ | ||
| module: ADAPTER, | ||
| method: "stream", | ||
| body: ProviderShared.encodeJson(event), | ||
| reason, | ||
| }); | ||
| const mapFinishReason = Effect.fn("OpenAIChat.mapFinishReason")(function* (event, reason) { | ||
| switch (reason) { | ||
| case "error": | ||
| return yield* finishReasonError(event, new UnknownProviderReason({ message: "Provider reported an error (finish_reason: error)" })); | ||
| case "network_error": | ||
| return yield* finishReasonError(event, new ProviderInternalReason({ message: "Provider reported a network error (finish_reason: network_error)" })); | ||
| case "stop": | ||
| case "end": | ||
| return "stop"; | ||
| case "length": | ||
| return "length"; | ||
| case "content_filter": | ||
| return "content-filter"; | ||
| case "function_call": | ||
| case "tool_calls": | ||
| return "tool-calls"; | ||
| default: | ||
| return "unknown"; | ||
| } | ||
| }); | ||
| // OpenAI Chat reports `prompt_tokens` (inclusive total) with a | ||
@@ -692,6 +705,8 @@ // cached-read and cache-write subsets, and `completion_tokens` (inclusive | ||
| const step = (state, event) => Effect.gen(function* () { | ||
| if (event.error) | ||
| if (event.error) { | ||
| const body = ProviderShared.encodeJson(event); | ||
| return yield* new AIError({ | ||
| module: ADAPTER, | ||
| method: "stream", | ||
| body, | ||
| reason: classifyProviderFailure({ | ||
@@ -701,4 +716,6 @@ message: event.error.message, | ||
| status: typeof event.error.code === "number" ? event.error.code : undefined, | ||
| rawBody: body, | ||
| }), | ||
| }); | ||
| } | ||
| const events = []; | ||
@@ -711,4 +728,7 @@ const choice = event.choices?.[0]; | ||
| const rawFinishReason = choice?.finish_reason; | ||
| const finishReason = rawFinishReason !== undefined && rawFinishReason !== null | ||
| ? { normalized: mapFinishReason(rawFinishReason), raw: choice?.native_finish_reason ?? rawFinishReason } | ||
| const finishReason = rawFinishReason | ||
| ? { | ||
| normalized: yield* mapFinishReason(event, rawFinishReason), | ||
| raw: choice?.native_finish_reason ?? rawFinishReason, | ||
| } | ||
| : state.finishReason; | ||
@@ -730,3 +750,3 @@ const delta = choice?.delta; | ||
| if (hasLateContent) | ||
| return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason"); | ||
| return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat received content after the finish reason", ProviderShared.encodeJson(event)); | ||
| return [{ ...state, usage }, events]; | ||
@@ -783,3 +803,3 @@ } | ||
| if (ToolStream.isError(result)) | ||
| return yield* result; | ||
| return yield* ProviderShared.eventError(ADAPTER, result.reason.message, ProviderShared.encodeJson(event)); | ||
| tools = result.tools; | ||
@@ -791,3 +811,3 @@ if (result.events.length) | ||
| if (finishReason !== undefined && state.finishReason === undefined && Object.keys(pendingTools).length > 0) | ||
| return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat tool call delta is missing id or name"); | ||
| return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat tool call delta is missing id or name", ProviderShared.encodeJson(event)); | ||
| // Finalize accumulated tool inputs eagerly when finish_reason arrives so | ||
@@ -812,2 +832,3 @@ // valid calls and malformed local calls settle independently. | ||
| nextToolIndex, | ||
| requireFinishReason: state.requireFinishReason, | ||
| }, | ||
@@ -817,6 +838,16 @@ events, | ||
| }); | ||
| const finishEvents = (state) => { | ||
| const finishEvents = Effect.fn("OpenAIChat.finishEvents")(function* (state) { | ||
| if (state.finishReason === undefined && state.requireFinishReason) | ||
| return yield* new AIError({ | ||
| module: ADAPTER, | ||
| method: "stream", | ||
| reason: new InvalidProviderOutputReason({ | ||
| classification: "incomplete-stream", | ||
| message: "OpenAI Chat stream ended without finish_reason", | ||
| route: ADAPTER, | ||
| }), | ||
| }); | ||
| const events = []; | ||
| const toolCallEvents = state.finishReason === undefined && Object.keys(state.tools).length > 0 | ||
| ? Effect.runSync(ToolStream.finishAll(ADAPTER, state.tools)).events | ||
| ? (yield* ToolStream.finishAll(ADAPTER, state.tools)).events | ||
| : state.toolCallEvents; | ||
@@ -829,3 +860,3 @@ const hasToolCalls = toolCallEvents.length > 0; | ||
| } | ||
| : { normalized: hasToolCalls ? "tool-calls" : "unknown" }; | ||
| : { normalized: hasToolCalls ? "tool-calls" : "stop" }; | ||
| const metadata = reasoningMetadata(state.reasoningField, state.reasoningDetailsObserved ? state.reasoningDetails : undefined); | ||
@@ -840,3 +871,3 @@ const started = state.reasoningDetailsObserved && !state.reasoningEmitted | ||
| return events; | ||
| }; | ||
| }); | ||
| // ============================================================================= | ||
@@ -869,2 +900,3 @@ // Protocol And OpenAI Route | ||
| nextToolIndex: 0, | ||
| requireFinishReason: request.model.compatibility?.requireFinishReason ?? true, | ||
| }), | ||
@@ -871,0 +903,0 @@ step, |
@@ -312,2 +312,3 @@ import { Schema } from "effect"; | ||
| } | undefined; | ||
| readonly arguments?: string | undefined; | ||
| readonly param?: string | null | undefined; | ||
@@ -314,0 +315,0 @@ readonly status_code?: unknown; |
@@ -169,2 +169,3 @@ import { Protocol } from "../route/protocol.js"; | ||
| } | undefined; | ||
| readonly arguments?: string | undefined; | ||
| readonly param?: string | null | undefined; | ||
@@ -171,0 +172,0 @@ readonly status_code?: unknown; |
@@ -286,3 +286,5 @@ import { Schema } from "effect"; | ||
| }, string, { | ||
| readonly [x: string]: unknown; | ||
| readonly error?: { | ||
| readonly [x: string]: unknown; | ||
| readonly message: string; | ||
@@ -289,0 +291,0 @@ readonly code?: string | number | null | undefined; |
+11
-1
@@ -126,3 +126,13 @@ import { Cause, Context, Effect, Layer, Schema, Stream } from "effect"; | ||
| const events = execution.frames.pipe(Stream.mapEffect(decodeEvent(route)), protocol.stream.terminal ? Stream.takeUntil(protocol.stream.terminal) : (stream) => stream); | ||
| const stream = events.pipe(Stream.mapAccumEffect(() => protocol.stream.initial(request), protocol.stream.step, protocol.stream.onHalt ? { onHalt: protocol.stream.onHalt } : undefined), Stream.catchCause((cause) => Stream.fail(streamError(route, `Failed to read ${route} stream`, cause))), requireTerminalEvent(route)); | ||
| const stream = Stream.suspend(() => { | ||
| let state = protocol.stream.initial(request); | ||
| const parsed = events.pipe(Stream.mapEffect((event) => protocol.stream.step(state, event).pipe(Effect.map(([next, output]) => { | ||
| state = next; | ||
| return output; | ||
| }))), Stream.flatMap(Stream.fromIterable)); | ||
| const onHalt = protocol.stream.onHalt; | ||
| return onHalt | ||
| ? parsed.pipe(Stream.concat(Stream.suspend(() => Stream.unwrap(onHalt(state).pipe(Effect.map(Stream.fromIterable)))))) | ||
| : parsed; | ||
| }).pipe(Stream.catchCause((cause) => Stream.fail(streamError(route, `Failed to read ${route} stream`, cause))), requireTerminalEvent(route)); | ||
| return execution.complete ? stream.pipe(Stream.onEnd(execution.complete)) : stream; | ||
@@ -129,0 +139,0 @@ }))); |
@@ -59,4 +59,4 @@ import { Schema, type Effect } from "effect"; | ||
| readonly terminal?: (event: Event) => boolean; | ||
| /** Optional flush emitted when the framed stream ends. */ | ||
| readonly onHalt?: (state: State) => ReadonlyArray<LLMEvent>; | ||
| /** Optional effectful flush emitted when the framed stream ends. */ | ||
| readonly onHalt?: (state: State) => Effect.Effect<ReadonlyArray<LLMEvent>, AIError>; | ||
| } | ||
@@ -63,0 +63,0 @@ /** |
+3
-3
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "version": "0.0.0-beta-18148", | ||
| "version": "0.0.0-beta-18155", | ||
| "name": "@opencode-ai/ai", | ||
@@ -33,3 +33,3 @@ "type": "module", | ||
| "@effect/platform-node": "4.0.0-rc.111", | ||
| "@opencode-ai/http-recorder": "0.0.0-beta-18148", | ||
| "@opencode-ai/http-recorder": "0.0.0-beta-18155", | ||
| "@tsconfig/bun": "1.0.9", | ||
@@ -43,3 +43,3 @@ "@types/bun": "1.3.13", | ||
| "@smithy/util-utf8": "4.2.2", | ||
| "@opencode-ai/schema": "0.0.0-beta-18148", | ||
| "@opencode-ai/schema": "0.0.0-beta-18155", | ||
| "aws4fetch": "1.0.20", | ||
@@ -46,0 +46,0 @@ "effect": "4.0.0-rc.111", |
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
1264038
0.29%29032
0.22%+ Added
- Removed