New:Socket for Asana Is Now Available.Learn more
Get Started

@opencode-ai/ai

Package Overview
Dependencies
Maintainers
2
Versions
1238
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@opencode-ai/ai - npm Package Compare versions

Comparing version
0.0.0-beta-18965
to
0.0.0-beta-18985
+69
-67
dist/protocols/open-responses.js

@@ -220,2 +220,7 @@ import { Effect, Option, Schema } from "effect";

});
// The spec requires `id` on every output item, but some gateways drop it from
// later item events (Bedrock Mantle renames it to `item_id` on
// `output_item.done` and `response.completed.output`). Decode it as optional
// and let `normalize` recover or mint it once before the parser runs.
// https://www.openresponses.org/specification#extending-items
export const StreamItem = Schema.StructWithRest(Schema.Struct({

@@ -645,3 +650,2 @@ type: Schema.String,

});
const isReasoningItem = (item) => item.type === "reasoning" && typeof item.id === "string";
const NO_EVENTS = [];

@@ -679,3 +683,24 @@ // `response.completed` / `response.incomplete` are clean finishes that emit a

};
export const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);
const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);
const ITEM_ID_PREFIX = {
message: "msg",
reasoning: "rs",
function_call: "fc",
compaction: "cmp",
};
// An item without an id adopts the id already open in its output slot,
// otherwise it gets a locally minted one.
const resolveItem = (state, item, index) => ({
...item,
id: item.id ??
(index === undefined ? undefined : state.outputItems[index]) ??
`${ITEM_ID_PREFIX[item.type] ?? "item"}_${crypto.randomUUID().replaceAll("-", "")}`,
});
// Registered output slots are authoritative for `item_id` routing, and items
// are resolved here so everything downstream can rely on `item.id`.
export const normalize = (state, input) => ({
...input,
item_id: input.item_id === undefined ? undefined : outputItemID(state, input),
item: input.item ? resolveItem(state, input.item, input.output_index) : input.item,
});
const startReasoningSummaryPart = (state, itemID, index) => {

@@ -756,9 +781,10 @@ const item = state.reasoningItems[itemID];

const item = event.item;
if (item?.type === "message" && item.id !== undefined) {
const itemID = item.id;
if (state.completedMessages.has(itemID))
if (!item)
return [state, NO_EVENTS];
if (item.type === "message") {
if (state.completedMessages.has(item.id))
return [state, NO_EVENTS];
const phase = messagePhase(item.phase);
const completedMessages = new Set(state.completedMessages);
if (state.message !== undefined && state.message.id !== itemID)
if (state.message !== undefined && state.message.id !== item.id)
completedMessages.add(state.message.id);

@@ -768,3 +794,3 @@ // A new message closes earlier messages, including ones that never streamed.

const lifecycle = [...state.lifecycle.text]
.filter((id) => id !== itemID)
.filter((id) => id !== item.id)
.reduce((lifecycle, id) => {

@@ -781,4 +807,4 @@ completedMessages.add(id);

message: {
id: itemID,
phase: phase === undefined && state.message?.id === itemID ? state.message.phase : phase,
id: item.id,
phase: phase === undefined && state.message?.id === item.id ? state.message.phase : phase,
},

@@ -789,3 +815,3 @@ },

}
if (item && isReasoningItem(item)) {
if (item.type === "reasoning") {
if (state.reasoningItems[item.id] !== undefined)

@@ -811,8 +837,7 @@ return [state, NO_EVENTS];

}
if (item?.type !== "function_call" || !item.call_id)
if (item.type !== "function_call" || !item.call_id)
return [state, NO_EVENTS];
const id = item.id ?? item.call_id;
if (Object.values(state.tools).some((tool) => tool?.id === item.call_id) || state.completedTools.has(item.call_id))
if (state.tools[item.id] !== undefined || state.completedTools.has(item.id))
return [state, NO_EVENTS];
const metadata = item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined;
const metadata = providerMetadata(state, { itemId: item.id });
const events = [];

@@ -824,3 +849,3 @@ const lifecycle = Lifecycle.stepStart(state.lifecycle, events);

lifecycle,
tools: ToolStream.start(state.tools, id, {
tools: ToolStream.start(state.tools, item.id, {
id: item.call_id,

@@ -894,4 +919,4 @@ name: item.name ?? "",

if (item.type === "compaction") {
if (!item.id || typeof item.encrypted_content !== "string")
return yield* ProviderShared.eventError(state.id, "Compaction output is missing its id or encrypted content");
if (typeof item.encrypted_content !== "string")
return yield* ProviderShared.eventError(state.id, "Compaction output is missing its encrypted content");
if (state.completedCompactions.has(item.id))

@@ -911,3 +936,3 @@ return [state, NO_EVENTS];

}
if (item.type === "message" && item.id !== undefined) {
if (item.type === "message") {
if (state.completedMessages.has(item.id))

@@ -947,29 +972,18 @@ return [state, NO_EVENTS];

return [state, NO_EVENTS];
const callID = item.call_id;
if (state.completedTools.has(callID))
if (state.completedTools.has(item.id))
return [state, NO_EVENTS];
const metadata = item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined;
const fallback = item.id ?? callID;
// Match the pending tool by call id so item events that disagree on
// whether `item.id` is present still resolve the same call.
const registered = state.tools[fallback] !== undefined
? fallback
: Object.keys(state.tools).find((key) => state.tools[key]?.id === callID);
const id = registered ?? fallback;
const tools = registered !== undefined
const metadata = providerMetadata(state, { itemId: item.id });
const registered = state.tools[item.id] !== undefined;
const tools = registered
? state.tools
: ToolStream.start(state.tools, id, {
id: callID,
name: item.name,
providerMetadata: metadata,
});
: ToolStream.start(state.tools, item.id, { id: item.call_id, name: item.name, providerMetadata: metadata });
const result = item.arguments === undefined
? yield* ToolStream.finish(state.id, tools, id)
: yield* ToolStream.finishWithInput(state.id, tools, id, item.arguments);
? yield* ToolStream.finish(state.id, tools, item.id)
: yield* ToolStream.finishWithInput(state.id, tools, item.id, item.arguments);
const events = [];
const finished = result.events ?? [];
// A done-only call never streamed a start event, so open its lifecycle here.
const resultEvents = registered !== undefined || finished.length === 0
const resultEvents = registered || finished.length === 0
? finished
: [LLMEvent.toolInputStart({ id: callID, name: item.name, providerMetadata: metadata }), ...finished];
: [LLMEvent.toolInputStart({ id: item.call_id, name: item.name, providerMetadata: metadata }), ...finished];
const lifecycle = resultEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;

@@ -984,3 +998,3 @@ events.push(...resultEvents);

tools: result.tools,
completedTools: new Set([...state.completedTools, callID]),
completedTools: new Set([...state.completedTools, item.id]),
},

@@ -990,3 +1004,3 @@ events,

}
if (isReasoningItem(item)) {
if (item.type === "reasoning") {
if (state.reasoningItems[item.id]?.open === false)

@@ -1074,13 +1088,10 @@ return [state, NO_EVENTS];

if (event.type === "response.completed") {
for (const item of event.response?.output ?? []) {
if (item.type !== "compaction" && item.type !== "function_call")
// An output item's array position is its output index.
for (const item of (event.response?.output ?? []).map((item, index) => resolveItem(state, item, index))) {
// Terminal recovery cannot insert a checkpoint before already-emitted content.
if (item.type === "compaction" && state.lifecycle.stepStarted && !state.completedCompactions.has(item.id))
return yield* ProviderShared.eventError(state.id, "Cannot recover a compaction checkpoint after output has been emitted");
const recoverable = item.type === "compaction" || (item.type === "function_call" && current.tools[item.id] !== undefined);
if (!recoverable)
continue;
if (item.type === "compaction") {
// Terminal recovery cannot insert a checkpoint before already-emitted content.
if (state.lifecycle.stepStarted && !state.completedCompactions.has(item.id ?? ""))
return yield* ProviderShared.eventError(state.id, "Cannot recover a compaction checkpoint after output has been emitted");
}
if (item.type === "function_call" &&
(!item.call_id || !Object.values(current.tools).some((tool) => tool?.id === item.call_id)))
continue;
const [next, emitted] = yield* onOutputItemDone(current, item);

@@ -1146,7 +1157,5 @@ current = next;

};
export const step = (state, input) => {
// The OpenAPI requires string IDs but imposes no minLength; empty is not missing.
const event = input.item_id !== undefined && outputItemID(state, input) !== input.item_id
? { ...input, item_id: outputItemID(state, input) }
: input;
// Callers must pass events through `normalize` first. The OpenAPI requires
// string IDs but imposes no minLength; empty is not missing.
export const step = (state, event) => {
if (event.type === "response.output_text.delta" || event.type === "response.output_text.done") {

@@ -1188,12 +1197,8 @@ if (event.item_id === undefined)

if (event.type === "response.output_item.added") {
if (event.item?.type === "message" && event.item.id === undefined)
return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
if (event.item &&
isReasoningItem(event.item) &&
if (event.item?.type === "reasoning" &&
state.reasoningItems[event.item.id] === undefined &&
state.lifecycle.reasoning.size > 0)
return ProviderShared.eventError(state.id, `${event.type} started reasoning before the previous item ended`);
const id = event.item?.id ?? (event.item?.type === "function_call" ? event.item.call_id : undefined);
return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && id !== undefined
? { ...state, outputItems: { ...state.outputItems, [event.output_index]: id } }
return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && event.item
? { ...state, outputItems: { ...state.outputItems, [event.output_index]: event.item.id } }
: state, event));

@@ -1205,7 +1210,4 @@ }

: ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
if (event.type === "response.output_item.done") {
if (event.item?.type === "message" && event.item.id === undefined)
return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
if (event.type === "response.output_item.done")
return onOutputItemDone(state, event.item);
}
if (event.type === "response.completed" || event.type === "response.incomplete")

@@ -1250,3 +1252,3 @@ return onResponseFinish(state, event);

initial,
step,
step: (state, event) => step(state, normalize(state, event)),
terminal,

@@ -1253,0 +1255,0 @@ },

@@ -157,6 +157,7 @@ import { Effect, Encoding, Schema } from "effect";

};
const step = (state, event) => {
const step = (state, input) => {
const event = OpenResponses.normalize(state, input);
if (event.type === "response.reasoning_text.delta")
return event.item_id !== undefined
? Effect.succeed(OpenResponses.onReasoningDelta(state, event, OpenResponses.outputItemID(state, event) ?? event.item_id))
? Effect.succeed(OpenResponses.onReasoningDelta(state, event, event.item_id))
: ProviderShared.eventError(ADAPTER, `${event.type} is missing item_id`);

@@ -163,0 +164,0 @@ if (event.type === "response.output_item.done" && event.item && ResponsesHostedTools.isItem(event.item, HOSTED_TOOLS))

import { Effect } from "effect";
import { type AIError, type ToolResultPart } from "../../schema/index.js";
import { OpenResponses } from "../open-responses.js";
export type Item = OpenResponses.StreamItem & {
readonly id: string;
export type Item = OpenResponses.OutputItem & {
readonly status?: string;

@@ -25,4 +24,4 @@ readonly action?: unknown;

export type Definitions = Readonly<Record<string, Definition>>;
export declare const isItem: <Tools extends Definitions>(item: OpenResponses.StreamItem, tools: Tools) => item is Item;
export declare const isItem: <Tools extends Definitions>(item: OpenResponses.OutputItem, tools: Tools) => item is Item;
export declare const onDone: (state: OpenResponses.ParserState, item: Item, tools: Definitions) => Effect.Effect<OpenResponses.StepResult, AIError>;
export * as ResponsesHostedTools from "./responses-hosted-tools.js";

@@ -5,3 +5,3 @@ import { Effect } from "effect";

import { Lifecycle } from "./lifecycle.js";
export const isItem = (item, tools) => item.type in tools && typeof item.id === "string" && item.id.length > 0;
export const isItem = (item, tools) => item.type in tools;
export const onDone = Effect.fn("ResponsesHostedTools.onDone")(function* (state, item, tools) {

@@ -8,0 +8,0 @@ const tool = tools[item.type];

@@ -56,3 +56,4 @@ import { Effect, Schema } from "effect";

// handled by the baseline); only its hosted tool vocabulary differs.
const step = (state, event) => {
const step = (state, input) => {
const event = OpenResponses.normalize(state, input);
if (event.type === "response.output_item.done" && event.item && ResponsesHostedTools.isItem(event.item, HOSTED_TOOLS))

@@ -59,0 +60,0 @@ return ResponsesHostedTools.onDone(state, event.item, HOSTED_TOOLS);

{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.0.0-beta-18965",
"version": "0.0.0-beta-18985",
"name": "@opencode-ai/ai",

@@ -33,3 +33,3 @@ "type": "module",

"@effect/platform-node": "4.0.0-rc.112",
"@opencode-ai/http-recorder": "0.0.0-beta-18965",
"@opencode-ai/http-recorder": "0.0.0-beta-18985",
"@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-18965",
"@opencode-ai/schema": "0.0.0-beta-18985",
"aws4fetch": "1.0.20",

@@ -46,0 +46,0 @@ "effect": "4.0.0-rc.112",

Sorry, the diff of this file is too big to display