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

@opencode-ai/ai

Package Overview
Dependencies
Maintainers
2
Versions
1082
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-dev-18539
to
0.0.0-dev-18544
+1
-0
dist/protocols/open-responses.d.ts

@@ -568,2 +568,3 @@ import { Effect, Schema } from "effect";

readonly tools: ToolStream.State<string>;
readonly completedTools: ReadonlySet<string>;
readonly hasFunctionCall: boolean;

@@ -570,0 +571,0 @@ readonly lifecycle: Lifecycle.State;

+130
-48

@@ -628,15 +628,25 @@ import { Effect, Schema } from "effect";

export const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);
export const onReasoningDelta = (state, event, itemID) => {
const startReasoningSummaryPart = (state, itemID, index) => {
const item = state.reasoningItems[itemID];
if (!event.delta || !item)
if (!item || index === 0 || item.summaryParts[index] !== undefined)
return [state, NO_EVENTS];
const index = event.summary_index ?? 0;
if (Object.values(item.summaryParts).every((status) => status === "concluded"))
return [state, NO_EVENTS];
const events = [];
const lifecycle = Object.entries(item.summaryParts)
.filter((entry) => entry[1] !== "concluded")
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${itemID}:${entry[0]}`, providerMetadata(state, { itemId: itemID })), state.lifecycle);
return [
{
...state,
lifecycle: Lifecycle.reasoningDelta(state.lifecycle, events, `${itemID}:${index}`, event.delta),
lifecycle: Lifecycle.reasoningStart(lifecycle, events, `${itemID}:${index}`, providerMetadata(state, { itemId: itemID, reasoningEncryptedContent: item.encryptedContent ?? null })),
reasoningItems: {
...state.reasoningItems,
[itemID]: { ...item, deltaIndexes: new Set([...item.deltaIndexes, index]) },
[itemID]: {
...item,
summaryParts: {
...Object.fromEntries(Object.entries(item.summaryParts).map((entry) => entry[1] === "concluded" ? entry : [entry[0], "concluded"])),
[index]: "active",
},
},
},

@@ -647,2 +657,30 @@ },

};
export const onReasoningDelta = (state, event, itemID) => {
const item = state.reasoningItems[itemID];
if (!event.delta || !item)
return [state, NO_EVENTS];
const index = event.summary_index ?? 0;
if (item.summaryParts[index] === "concluded")
return [state, NO_EVENTS];
// An unseen index cannot reopen an item whose parts have all concluded.
if (item.summaryParts[index] === undefined &&
Object.values(item.summaryParts).every((status) => status === "concluded"))
return [state, NO_EVENTS];
const started = item.summaryParts[index] === undefined ? startReasoningSummaryPart(state, itemID, index) : [state, NO_EVENTS];
const current = started[0].reasoningItems[itemID];
if (!current)
return started;
const events = [...started[1]];
return [
{
...started[0],
lifecycle: Lifecycle.reasoningDelta(started[0].lifecycle, events, `${itemID}:${index}`, event.delta),
reasoningItems: {
...started[0].reasoningItems,
[itemID]: { ...current, deltaIndexes: new Set([...current.deltaIndexes, index]) },
},
},
events,
];
};
// Some compatible gateways emit a reasoning final without streaming any

@@ -661,3 +699,3 @@ // deltas, mirroring `response.output_text.done`. Reconcile the complete text

const reasoningMetadata = (state, item) => providerMetadata(state, { itemId: item.id, reasoningEncryptedContent: item.encrypted_content ?? null });
// Responses APIs stream reasoning items in a stable order:
// Responses APIs normally stream reasoning items in this order:
// `output_item.added` (reasoning) →

@@ -669,21 +707,34 @@ // `reasoning_summary_part.added` (index=0) →

// `output_item.done` (reasoning).
// The handlers below rely on this ordering: `onOutputItemAdded` seeds the
// per-item entry, `onReasoningSummaryPartAdded` for `summary_index === 0`
// short-circuits when the entry already exists, and higher-index handlers
// fold against the same entry. Behaviour for out-of-order events is
// best-effort, not guaranteed.
// `onOutputItemAdded` seeds the per-item entry, while each later part start is
// also an implicit boundary for the previous part. This keeps the common event
// lifecycle ordered when a compatible provider omits or delays a part-done event.
const onOutputItemAdded = (state, event) => {
const item = event.item;
if (item?.type === "message" && item.id !== undefined) {
const itemID = item.id;
const phase = messagePhase(item.phase);
// A new message item is an implicit boundary for every earlier message
// item: text still streaming is ended, and all older items leave the
// tracked set so their late deltas stay no-ops instead of overlapping.
const events = [];
const lifecycle = [...state.lifecycle.text]
.filter((id) => id !== itemID)
.reduce((lifecycle, id) => {
const openPhase = state.messagePhases[id];
return Lifecycle.textEnd(lifecycle, events, id, providerMetadata(state, { itemId: id, ...(openPhase === undefined ? {} : { phase: openPhase }) }));
}, state.lifecycle);
const nextPhase = phase === undefined ? state.messagePhases[itemID] : phase;
return [
{
...state,
messageItems: new Set([...state.messageItems, item.id]),
messagePhases: phase === undefined ? state.messagePhases : { ...state.messagePhases, [item.id]: phase },
lifecycle,
messageItems: new Set([itemID]),
messagePhases: nextPhase === undefined ? {} : { [itemID]: nextPhase },
},
NO_EVENTS,
events,
];
}
if (item && isReasoningItem(item)) {
if (state.reasoningItems[item.id] !== undefined)
return [state, NO_EVENTS];
const events = [];

@@ -709,2 +760,6 @@ return [

const id = item.id ?? item.call_id;
// Pending tools always store the call id, so this also catches duplicates
// that disagree on whether `item.id` is present.
if (Object.values(state.tools).some((tool) => tool?.id === item.call_id) || state.completedTools.has(item.call_id))
return [state, NO_EVENTS];
const metadata = item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined;

@@ -730,28 +785,3 @@ const events = [];

return [state, NO_EVENTS];
const item = state.reasoningItems[event.item_id];
if (!item)
return [state, NO_EVENTS];
if (event.summary_index === 0)
return [state, NO_EVENTS];
const events = [];
const closed = Object.entries(item.summaryParts)
.filter((entry) => entry[1] === "can-conclude")
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${event.item_id}:${entry[0]}`, providerMetadata(state, { itemId: event.item_id })), state.lifecycle);
return [
{
...state,
lifecycle: Lifecycle.reasoningStart(closed, events, `${event.item_id}:${event.summary_index}`, providerMetadata(state, { itemId: event.item_id, reasoningEncryptedContent: item.encryptedContent ?? null })),
reasoningItems: {
...state.reasoningItems,
[event.item_id]: {
...item,
summaryParts: {
...Object.fromEntries(Object.entries(item.summaryParts).map((entry) => entry[1] === "can-conclude" ? [entry[0], "concluded"] : entry)),
[event.summary_index]: "active",
},
},
},
},
events,
];
return startReasoningSummaryPart(state, event.item_id, event.summary_index);
};

@@ -764,2 +794,4 @@ const onReasoningSummaryPartDone = (state, event) => {

return [state, NO_EVENTS];
if (item.summaryParts[event.summary_index] !== "active")
return [state, NO_EVENTS];
return [

@@ -831,9 +863,19 @@ {

return [state, NO_EVENTS];
const id = item.id ?? item.call_id;
const tools = state.tools[id]
const callID = item.call_id;
if (state.completedTools.has(callID))
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
? state.tools
: ToolStream.start(state.tools, id, {
id: item.call_id,
id: callID,
name: item.name,
providerMetadata: item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined,
providerMetadata: metadata,
});

@@ -844,3 +886,7 @@ const result = item.arguments === undefined

const events = [];
const resultEvents = result.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
? finished
: [LLMEvent.toolInputStart({ id: callID, name: item.name, providerMetadata: metadata }), ...finished];
const lifecycle = resultEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;

@@ -855,2 +901,3 @@ events.push(...resultEvents);

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

@@ -868,4 +915,19 @@ events,

.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${entry[0]}`, metadata), state.lifecycle);
const { [item.id]: _removed, ...reasoningItems } = state.reasoningItems;
return [{ ...state, lifecycle, reasoningItems }, events];
// Keep the fully-concluded entry so duplicate or late events for this
// item remain no-ops instead of reopening lifecycle state.
return [
{
...state,
lifecycle,
reasoningItems: {
...state.reasoningItems,
[item.id]: {
...reasoningItem,
encryptedContent: item.encrypted_content ?? reasoningItem.encryptedContent,
summaryParts: Object.fromEntries(Object.keys(reasoningItem.summaryParts).map((index) => [index, "concluded"])),
},
},
},
events,
];
}

@@ -876,3 +938,17 @@ if (!state.lifecycle.reasoning.has(item.id)) {

events.push(LLMEvent.reasoningEnd({ id: item.id, providerMetadata: metadata }));
return [{ ...state, lifecycle }, events];
return [
{
...state,
lifecycle,
reasoningItems: {
...state.reasoningItems,
[item.id]: {
encryptedContent: item.encrypted_content,
summaryParts: { 0: "concluded" },
deltaIndexes: new Set(),
},
},
},
events,
];
}

@@ -994,2 +1070,7 @@ return [

return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
if (event.item &&
isReasoningItem(event.item) &&
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);

@@ -1030,2 +1111,3 @@ return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && id !== undefined

tools: ToolStream.empty(),
completedTools: new Set(),
lifecycle: Lifecycle.initial(),

@@ -1032,0 +1114,0 @@ outputItems: {},

{
"$schema": "https://json.schemastore.org/package.json",
"version": "0.0.0-dev-18539",
"version": "0.0.0-dev-18544",
"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-dev-18539",
"@opencode-ai/http-recorder": "0.0.0-dev-18544",
"@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-dev-18539",
"@opencode-ai/schema": "0.0.0-dev-18544",
"aws4fetch": "1.0.20",

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