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

@opencode-ai/ai

Package Overview
Dependencies
Maintainers
2
Versions
1086
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-18600
to
0.0.0-beta-18684
+72
-25
dist/protocols/open-responses.js

@@ -1,2 +0,2 @@

import { Effect, Schema } from "effect";
import { Effect, Option, Schema } from "effect";
import { HttpTransport } from "../route/transport/index.js";

@@ -627,2 +627,10 @@ import { Protocol } from "../route/protocol.js";

};
const decodeMessagePart = Schema.decodeUnknownOption(Schema.Union([OpenResponsesOutputText, Schema.Struct({ type: Schema.tag("refusal"), refusal: Schema.String })]));
const decodeSummaryPart = Schema.decodeUnknownOption(OpenResponsesReasoningSummaryText);
const decodeReasoningPart = Schema.decodeUnknownOption(Schema.Struct({ type: Schema.tag("reasoning_text"), text: Schema.String }));
const joinReasoningText = (parts) => {
if (!parts.some((part) => part !== undefined && part.length > 0))
return undefined;
return parts.filter((part) => part !== undefined).join("\n\n");
};
export const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);

@@ -825,15 +833,26 @@ const startReasoningSummaryPart = (state, itemID, index) => {

});
const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (state, event) {
const item = event.item;
const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (state, item) {
if (!item)
return [state, NO_EVENTS];
if (item.type === "message" && item.id !== undefined) {
const message = state.message?.id === item.id ? state.message : undefined;
const itemPhase = messagePhase(item.phase);
const phase = itemPhase === undefined && state.message?.id === item.id ? state.message.phase : itemPhase;
const phase = itemPhase === undefined ? message?.phase : itemPhase;
const parts = Array.isArray(item.content) ? item.content : [];
const content = [];
for (const part of parts) {
const decoded = Option.getOrUndefined(decodeMessagePart(part));
if (!decoded)
continue;
content.push(decoded.type === "output_text" ? decoded.text : decoded.refusal);
}
const text = content.length > 0 ? content.join("") : undefined;
const metadata = providerMetadata(state, { itemId: item.id, ...(phase === undefined ? {} : { phase }) });
const events = [];
const lifecycle = message && text ? Lifecycle.textStart(state.lifecycle, events, item.id, metadata) : state.lifecycle;
return [
{
...state,
lifecycle: Lifecycle.textEnd(state.lifecycle, events, item.id, providerMetadata(state, { itemId: item.id, ...(phase === undefined ? {} : { phase }) })),
message: state.message?.id === item.id ? undefined : state.message,
lifecycle: Lifecycle.textEnd(lifecycle, events, item.id, metadata, text),
message: message ? undefined : state.message,
},

@@ -888,11 +907,32 @@ events,

if (isReasoningItem(item)) {
if (state.reasoningItems[item.id]?.open === false)
return [state, NO_EVENTS];
const metadata = reasoningMetadata(state, item);
const summaryParts = Array.isArray(item.summary) ? item.summary : [];
const summary = [];
for (const part of summaryParts) {
const decoded = Option.getOrUndefined(decodeSummaryPart(part));
// Keep missing entries so the array still matches the provider's summary indexes.
summary.push(decoded?.text);
}
const reasoningParts = Array.isArray(item.content) ? item.content : [];
const content = [];
for (const part of reasoningParts) {
const decoded = Option.getOrUndefined(decodeReasoningPart(part));
if (decoded)
content.push(decoded.text);
}
const itemText = joinReasoningText(summary) ?? joinReasoningText(content);
const events = [];
const metadata = reasoningMetadata(state, item);
const reasoningItem = state.reasoningItems[item.id];
if (reasoningItem) {
if (!reasoningItem.open)
return [state, NO_EVENTS];
const lifecycle = Object.entries(reasoningItem.summaryParts)
.filter((entry) => entry[1] === "active" || entry[1] === "can-conclude")
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${entry[0]}`, metadata), state.lifecycle);
const fragments = Object.entries(reasoningItem.summaryParts);
let lifecycle = state.lifecycle;
for (const [index, status] of fragments) {
if (status === "concluded")
continue;
// Do not repeat earlier summaries that were already emitted as separate fragments.
const finalText = fragments.length === 1 ? itemText : summary[Number(index)];
lifecycle = Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${index}`, metadata, finalText || undefined);
}
return [

@@ -917,3 +957,7 @@ {

events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata: metadata }));
events.push(LLMEvent.reasoningEnd({ id: item.id, providerMetadata: metadata }));
events.push(LLMEvent.reasoningEnd({
id: item.id,
providerMetadata: metadata,
text: itemText,
}));
return [

@@ -944,13 +988,16 @@ {

const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (state, event) {
const reconciled = event.type === "response.completed"
? yield* Effect.reduce(event.response?.output ?? [], () => [state, NO_EVENTS], ([current, events], item) => {
let current = state;
const events = [];
if (event.type === "response.completed") {
for (const item of event.response?.output ?? []) {
const id = item.id ?? (item.type === "function_call" ? item.call_id : undefined);
if (id === undefined ||
((item.type !== "function_call" || !current.tools[id]) &&
(item.type !== "reasoning" || !current.reasoningItems[id]?.open)))
return Effect.succeed([current, events]);
return onOutputItemDone(current, { type: "response.output_item.done", item }).pipe(Effect.map(([next, emitted]) => [next, [...events, ...emitted]]));
})
: [state, NO_EVENTS];
const current = reconciled[0];
if (id === undefined)
continue;
if (item.type !== "function_call" || !current.tools[id])
continue;
const [next, emitted] = yield* onOutputItemDone(current, item);
current = next;
events.push(...emitted);
}
}
// Some compatible providers omit output_item.done even after completing the response.

@@ -960,3 +1007,3 @@ const pending = event.type === "response.completed"

: { tools: current.tools, events: NO_EVENTS };
const events = [...reconciled[1], ...pending.events];
events.push(...pending.events);
const hasFunctionCall = pending.events.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||

@@ -1070,3 +1117,3 @@ current.hasFunctionCall;

return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
return onOutputItemDone(state, event);
return onOutputItemDone(state, event.item);
}

@@ -1073,0 +1120,0 @@ if (event.type === "response.completed" || event.type === "response.incomplete")

@@ -646,3 +646,8 @@ import { Effect, Schema } from "effect";

default:
return "unknown";
return yield* new AIError({
reason: new UnknownProviderError({
message: `Provider finish_reason: ${reason}`,
body: ProviderShared.encodeJson(event),
}),
});
}

@@ -850,7 +855,13 @@ });

}
if (finishReason !== undefined && state.finishReason === undefined && Object.keys(pendingTools).length > 0)
const incompleteTools = finishReason?.normalized === "content-filter" || finishReason?.normalized === "length";
if (finishReason !== undefined &&
!incompleteTools &&
state.finishReason === undefined &&
Object.keys(pendingTools).length)
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
// valid calls and malformed local calls settle independently.
const finished = finishReason !== undefined && state.finishReason === undefined && Object.keys(tools).length > 0
// Filtering or truncation terminates the response without confirming pending tool calls.
const finished = finishReason !== undefined &&
!incompleteTools &&
state.finishReason === undefined &&
Object.keys(tools).length > 0
? yield* ToolStream.finishAll(ADAPTER, tools)

@@ -857,0 +868,0 @@ : undefined;

@@ -58,2 +58,3 @@ import { Option, Schema } from "effect";

const CONTENT_POLICY_TEXT = /content[-_\s]?policy|content_filter|safety/i;
const SERVER_ERROR_TEXT = /\b(?:try again|(?:please |you can )?retry (?:the |this |your )?request|try (?:the |this |your )?request again|(?:currently |temporarily )?at capacity|overloaded|temporarily unavailable|service[-_\s]?unavailable|(?:server|internal)[-_\s]?error|server (?:is )?busy|provider returned (?:an )?error|resource[-_\s]?exhausted|upstream (?:connect|connection|request)|request buffer limit while retrying upstream)\b/i;
// Classification records affirmative evidence about a failure. Deterministic

@@ -98,2 +99,5 @@ // failures need positive identification (a 4xx status, quota/auth/policy

(input.status !== undefined && input.status >= 500) ||
((input.status === undefined || input.status < 400) &&
!codes.some((code) => INVALID_REQUEST_CODES.has(code)) &&
SERVER_ERROR_TEXT.test(text)) ||
codes.some((code) => SERVER_CODES.has(code) || code.includes("exhausted") || code.includes("unavailable")))

@@ -100,0 +104,0 @@ return new ProviderInternalError({

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

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