@opencode-ai/ai
Advanced tools
@@ -572,4 +572,6 @@ import { Effect, Schema } from "effect"; | ||
| readonly outputItems: Readonly<Record<number, string>>; | ||
| readonly messageItems: ReadonlySet<string>; | ||
| readonly messagePhases: Readonly<Record<string, MessagePhase | null>>; | ||
| readonly message: { | ||
| readonly id: string; | ||
| readonly phase: MessagePhase | null | undefined; | ||
| } | undefined; | ||
| readonly reasoningItems: Readonly<Record<string, ReasoningStreamItem>>; | ||
@@ -579,2 +581,3 @@ } | ||
| interface ReasoningStreamItem { | ||
| readonly open: boolean; | ||
| readonly encryptedContent: string | null | undefined; | ||
@@ -581,0 +584,0 @@ readonly summaryParts: Readonly<Record<number, ReasoningSummaryStatus>>; |
@@ -610,6 +610,6 @@ import { Effect, Schema } from "effect"; | ||
| const onOutputTextDelta = (state, event, id) => { | ||
| if (!event.delta || !state.messageItems.has(id)) | ||
| if (!event.delta || state.message?.id !== id) | ||
| return [state, NO_EVENTS]; | ||
| const events = []; | ||
| const phase = state.messagePhases[id]; | ||
| const phase = state.message.phase; | ||
| const metadata = providerMetadata(state, { itemId: id, ...(phase === undefined ? {} : { phase }) }); | ||
@@ -620,3 +620,3 @@ const lifecycle = Lifecycle.textStart(state.lifecycle, events, id, metadata); | ||
| const onOutputTextDone = (state, event, id) => { | ||
| if (state.messageItems.has(id)) { | ||
| if (state.message?.id === id) { | ||
| if (state.lifecycle.text.has(id) || event.text === undefined) | ||
@@ -632,6 +632,4 @@ return [state, NO_EVENTS]; | ||
| const item = state.reasoningItems[itemID]; | ||
| if (!item || index === 0 || item.summaryParts[index] !== undefined) | ||
| if (!item?.open || index === 0 || item.summaryParts[index] !== undefined) | ||
| return [state, NO_EVENTS]; | ||
| if (Object.values(item.summaryParts).every((status) => status === "concluded")) | ||
| return [state, NO_EVENTS]; | ||
| const events = []; | ||
@@ -661,3 +659,3 @@ const lifecycle = Object.entries(item.summaryParts) | ||
| const item = state.reasoningItems[itemID]; | ||
| if (!event.delta || !item) | ||
| if (!event.delta || !item?.open) | ||
| return [state, NO_EVENTS]; | ||
@@ -667,17 +665,13 @@ const index = event.summary_index ?? 0; | ||
| 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]; | ||
| const [started, emitted] = startReasoningSummaryPart(state, itemID, index); | ||
| const current = started.reasoningItems[itemID]; | ||
| if (!current) | ||
| return started; | ||
| const events = [...started[1]]; | ||
| return [started, emitted]; | ||
| const events = [...emitted]; | ||
| return [ | ||
| { | ||
| ...started[0], | ||
| lifecycle: Lifecycle.reasoningDelta(started[0].lifecycle, events, `${itemID}:${index}`, event.delta), | ||
| ...started, | ||
| lifecycle: Lifecycle.reasoningDelta(started.lifecycle, events, `${itemID}:${index}`, event.delta), | ||
| reasoningItems: { | ||
| ...started[0].reasoningItems, | ||
| ...started.reasoningItems, | ||
| [itemID]: { ...current, deltaIndexes: new Set([...current.deltaIndexes, index]) }, | ||
@@ -694,3 +688,3 @@ }, | ||
| const item = state.reasoningItems[itemID]; | ||
| if (!item || typeof event.text !== "string") | ||
| if (!item?.open || typeof event.text !== "string") | ||
| return [state, NO_EVENTS]; | ||
@@ -718,5 +712,3 @@ const index = event.summary_index ?? 0; | ||
| 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. | ||
| // A new message closes earlier messages, including ones that never streamed. | ||
| const events = []; | ||
@@ -726,6 +718,5 @@ const lifecycle = [...state.lifecycle.text] | ||
| .reduce((lifecycle, id) => { | ||
| const openPhase = state.messagePhases[id]; | ||
| const openPhase = state.message?.id === id ? state.message.phase : undefined; | ||
| 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 [ | ||
@@ -735,4 +726,6 @@ { | ||
| lifecycle, | ||
| messageItems: new Set([itemID]), | ||
| messagePhases: nextPhase === undefined ? {} : { [itemID]: nextPhase }, | ||
| message: { | ||
| id: itemID, | ||
| phase: phase === undefined && state.message?.id === itemID ? state.message.phase : phase, | ||
| }, | ||
| }, | ||
@@ -753,2 +746,3 @@ events, | ||
| [item.id]: { | ||
| open: true, | ||
| encryptedContent: item.encrypted_content, | ||
@@ -766,4 +760,2 @@ summaryParts: { 0: "active" }, | ||
| 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)) | ||
@@ -797,3 +789,3 @@ return [state, NO_EVENTS]; | ||
| const item = state.reasoningItems[event.item_id]; | ||
| if (!item) | ||
| if (!item?.open) | ||
| return [state, NO_EVENTS]; | ||
@@ -850,7 +842,4 @@ if (item.summaryParts[event.summary_index] !== "active") | ||
| const itemPhase = messagePhase(item.phase); | ||
| const phase = itemPhase === undefined ? state.messagePhases[item.id] : itemPhase; | ||
| const phase = itemPhase === undefined && state.message?.id === item.id ? state.message.phase : itemPhase; | ||
| const events = []; | ||
| const messageItems = new Set(state.messageItems); | ||
| messageItems.delete(item.id); | ||
| const { [item.id]: _phase, ...messagePhases } = state.messagePhases; | ||
| return [ | ||
@@ -860,4 +849,3 @@ { | ||
| lifecycle: Lifecycle.textEnd(state.lifecycle, events, item.id, providerMetadata(state, { itemId: item.id, ...(phase === undefined ? {} : { phase }) })), | ||
| messageItems, | ||
| messagePhases, | ||
| message: state.message?.id === item.id ? undefined : state.message, | ||
| }, | ||
@@ -916,7 +904,7 @@ events, | ||
| 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); | ||
| // Keep the fully-concluded entry so duplicate or late events for this | ||
| // item remain no-ops instead of reopening lifecycle state. | ||
| return [ | ||
@@ -930,4 +918,4 @@ { | ||
| ...reasoningItem, | ||
| open: false, | ||
| encryptedContent: item.encrypted_content ?? reasoningItem.encryptedContent, | ||
| summaryParts: Object.fromEntries(Object.keys(reasoningItem.summaryParts).map((index) => [index, "concluded"])), | ||
| }, | ||
@@ -950,2 +938,3 @@ }, | ||
| [item.id]: { | ||
| open: false, | ||
| encryptedContent: item.encrypted_content, | ||
@@ -973,3 +962,3 @@ summaryParts: { 0: "concluded" }, | ||
| ((item.type !== "function_call" || !current.tools[id]) && | ||
| (item.type !== "reasoning" || !current.reasoningItems[id]))) | ||
| (item.type !== "reasoning" || !current.reasoningItems[id]?.open))) | ||
| return Effect.succeed([current, events]); | ||
@@ -1119,4 +1108,3 @@ return onOutputItemDone(current, { type: "response.output_item.done", item }).pipe(Effect.map(([next, emitted]) => [next, [...events, ...emitted]])); | ||
| outputItems: {}, | ||
| messageItems: new Set(), | ||
| messagePhases: {}, | ||
| message: undefined, | ||
| reasoningItems: {}, | ||
@@ -1123,0 +1111,0 @@ }); |
+3
-3
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "version": "0.0.0-dev-18548", | ||
| "version": "0.0.0-dev-18549", | ||
| "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-18548", | ||
| "@opencode-ai/http-recorder": "0.0.0-dev-18549", | ||
| "@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-18548", | ||
| "@opencode-ai/schema": "0.0.0-dev-18549", | ||
| "aws4fetch": "1.0.20", | ||
@@ -46,0 +46,0 @@ "effect": "4.0.0-rc.112", |
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.
1474241
-0.06%34009
-0.03%+ Added
- Removed