mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-23 09:07:37 +00:00
Compare commits
8
Commits
v2
...
message-native
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
396d7a0fc2 | ||
|
|
873a6cb43a | ||
|
|
75a70dbd7e | ||
|
|
8997d8662f | ||
|
|
e4dd41b033 | ||
|
|
347bf1d749 | ||
|
|
e7c4bffd38 | ||
|
|
85962e49b7 |
@@ -64,7 +64,7 @@ type ToolSeed = {
|
||||
name: string
|
||||
messageID?: string
|
||||
executed?: boolean
|
||||
providerState?: Record<string, unknown>
|
||||
native?: Record<string, unknown>
|
||||
providerResultState?: Record<string, unknown>
|
||||
state:
|
||||
| { status: "streaming"; input: Record<string, unknown>; raw: string }
|
||||
@@ -406,7 +406,7 @@ export function partUpdated(part: PartSeed<"assistant">): readonly OpenCodeEvent
|
||||
sessionID,
|
||||
assistantMessageID: messageID,
|
||||
ordinal: ref.ordinal!,
|
||||
state: jsonRecord(part.metadata),
|
||||
native: jsonRecord(part.metadata),
|
||||
}),
|
||||
]
|
||||
return [
|
||||
@@ -417,7 +417,7 @@ export function partUpdated(part: PartSeed<"assistant">): readonly OpenCodeEvent
|
||||
sessionID,
|
||||
assistantMessageID: messageID,
|
||||
ordinal: ref.ordinal!,
|
||||
state: jsonRecord(part.metadata),
|
||||
native: jsonRecord(part.metadata),
|
||||
}),
|
||||
]),
|
||||
makeEvent("session.reasoning.ended", {
|
||||
@@ -425,7 +425,7 @@ export function partUpdated(part: PartSeed<"assistant">): readonly OpenCodeEvent
|
||||
assistantMessageID: messageID,
|
||||
ordinal: ref.ordinal!,
|
||||
text: part.text,
|
||||
state: jsonRecord(part.metadata),
|
||||
native: jsonRecord(part.metadata),
|
||||
}),
|
||||
]
|
||||
}
|
||||
@@ -703,7 +703,7 @@ function messageContent(
|
||||
return {
|
||||
type: "reasoning",
|
||||
text: part.text,
|
||||
state: jsonRecord(part.metadata),
|
||||
native: jsonRecord(part.metadata),
|
||||
time: part.time
|
||||
? { created: part.time.start, ...(part.time.end === undefined ? {} : { completed: part.time.end }) }
|
||||
: undefined,
|
||||
@@ -721,7 +721,7 @@ function messageContent(
|
||||
...(completed === undefined ? {} : { completed }),
|
||||
},
|
||||
...(part.executed === undefined ? {} : { executed: part.executed }),
|
||||
...(part.providerState ? { providerState: jsonRecord(part.providerState) } : {}),
|
||||
...(part.native ? { native: jsonRecord(part.native) } : {}),
|
||||
...(part.providerResultState ? { providerResultState: jsonRecord(part.providerResultState) } : {}),
|
||||
}
|
||||
if (state.status === "streaming") return { ...base, state: { status: "streaming", input: state.raw } }
|
||||
@@ -791,7 +791,7 @@ function toolEvents(part: ToolSeed, messageID: string): readonly OpenCodeEvent[]
|
||||
id: part.id,
|
||||
input: part.state.input,
|
||||
executed: part.executed ?? true,
|
||||
state: jsonRecord(part.providerState),
|
||||
native: jsonRecord(part.native),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { OpenCode } from "@opencode/client"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { Session } from "@opencode/schema/session"
|
||||
import { SessionMessage } from "@opencode/schema/session-message"
|
||||
import { SessionTransfer } from "@opencode/schema/session-transfer"
|
||||
import { Effect, Option, Schema } from "effect"
|
||||
import { Effect, Option, Predicate, Schema } from "effect"
|
||||
import { EOL } from "node:os"
|
||||
import path from "node:path"
|
||||
import { Commands } from "../../commands"
|
||||
@@ -23,7 +24,13 @@ export default Runtime.handler(
|
||||
catch: (cause) =>
|
||||
new Error(`Failed to read session data: ${cause instanceof Error ? cause.message : String(cause)}`),
|
||||
})
|
||||
const data = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(SessionTransfer.Data))(text)
|
||||
const raw = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(text)
|
||||
// Exports written before provider blobs were renamed to `native` still carry the old keys.
|
||||
const data = yield* Schema.decodeUnknownEffect(SessionTransfer.Data)(
|
||||
Predicate.isObject(raw) && Array.isArray(raw.messages)
|
||||
? { ...raw, messages: raw.messages.map(SessionMessage.persisted) }
|
||||
: raw,
|
||||
)
|
||||
const encoded = Schema.encodeSync(SessionTransfer.Data)(data)
|
||||
const server = yield* ServerConnection.resolve({
|
||||
server: Option.getOrUndefined(input.server),
|
||||
|
||||
@@ -54,7 +54,7 @@ type ToolState = StartedPart & {
|
||||
input: Record<string, JsonValue>
|
||||
raw?: string
|
||||
provider?: unknown
|
||||
providerState?: SessionMessageAssistantTool["providerState"]
|
||||
native?: SessionMessageAssistantTool["native"]
|
||||
metadata: Record<string, JsonValue>
|
||||
content: ToolContent[]
|
||||
}
|
||||
@@ -290,7 +290,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
messageID: event.data.assistantMessageID,
|
||||
type: "reasoning",
|
||||
text: event.data.text,
|
||||
metadata: event.data.state,
|
||||
metadata: event.data.native,
|
||||
time: { start: started?.timestamp ?? time, end: time },
|
||||
}
|
||||
renderedReasoning.set(key, event.data.text)
|
||||
@@ -332,8 +332,8 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
tool: current?.tool ?? "tool",
|
||||
input: event.data.input,
|
||||
raw: current?.raw,
|
||||
provider: { executed: event.data.executed, state: event.data.state },
|
||||
providerState: event.data.state,
|
||||
provider: { executed: event.data.executed, state: event.data.native },
|
||||
native: event.data.native,
|
||||
metadata: {},
|
||||
content: [],
|
||||
})
|
||||
@@ -354,7 +354,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
id: event.data.id,
|
||||
name: current.tool,
|
||||
executed: event.data.executed,
|
||||
providerState: current.providerState,
|
||||
native: current.native,
|
||||
providerResultState: event.data.resultState,
|
||||
state: {
|
||||
status: "completed",
|
||||
@@ -402,7 +402,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
id: event.data.id,
|
||||
name: current.tool,
|
||||
executed: event.data.executed,
|
||||
providerState: current.providerState,
|
||||
native: current.native,
|
||||
providerResultState: event.data.resultState,
|
||||
state: {
|
||||
status: "error",
|
||||
@@ -564,7 +564,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
messageID: message.id,
|
||||
type: "reasoning",
|
||||
text,
|
||||
metadata: item.state,
|
||||
metadata: item.native,
|
||||
time: { start: message.time.created, end: timestamp },
|
||||
}
|
||||
renderedReasoning.set(key, item.text)
|
||||
|
||||
@@ -851,7 +851,7 @@ export type SessionLogOutput =
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly rawFinish?: string | undefined
|
||||
readonly providerState?: SessionMessage.ProviderState | undefined
|
||||
readonly native?: SessionMessage.ProviderState | undefined
|
||||
readonly cost: number & Brand.Brand<"Money.USD">
|
||||
readonly tokens: {
|
||||
readonly input: number
|
||||
@@ -881,7 +881,7 @@ export type SessionLogOutput =
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly finish?: "content-filter" | undefined
|
||||
readonly rawFinish?: string | undefined
|
||||
readonly providerState?: SessionMessage.ProviderState | undefined
|
||||
readonly native?: SessionMessage.ProviderState | undefined
|
||||
readonly cost?: (number & Brand.Brand<"Money.USD">) | undefined
|
||||
readonly tokens?:
|
||||
| {
|
||||
@@ -930,7 +930,7 @@ export type SessionLogOutput =
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly ordinal: number
|
||||
readonly text: string
|
||||
readonly state?: SessionMessage.ProviderState | undefined
|
||||
readonly native?: SessionMessage.ProviderState | undefined
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -949,7 +949,7 @@ export type SessionLogOutput =
|
||||
readonly sessionID: Session.ID
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly ordinal: number
|
||||
readonly state?: SessionMessage.ProviderState | undefined
|
||||
readonly native?: SessionMessage.ProviderState | undefined
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -969,7 +969,7 @@ export type SessionLogOutput =
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly ordinal: number
|
||||
readonly text: string
|
||||
readonly state?: SessionMessage.ProviderState | undefined
|
||||
readonly native?: SessionMessage.ProviderState | undefined
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1028,7 +1028,7 @@ export type SessionLogOutput =
|
||||
readonly id: string
|
||||
readonly input: { readonly [x: string]: unknown }
|
||||
readonly executed: boolean
|
||||
readonly state?: SessionMessage.ProviderState | undefined
|
||||
readonly native?: SessionMessage.ProviderState | undefined
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1171,7 +1171,7 @@ export type SessionLogOutput =
|
||||
readonly sessionID: Session.ID
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly model?: Model.Ref | undefined
|
||||
readonly providerState?: SessionMessage.ProviderState | undefined
|
||||
readonly native?: SessionMessage.ProviderState | undefined
|
||||
readonly providerContext?:
|
||||
| {
|
||||
readonly version: 1
|
||||
|
||||
@@ -509,12 +509,12 @@ export type PromptAgentAttachment = { name: string; mention?: PromptMention }
|
||||
|
||||
export type PromptSkillAttachment = { id: string; name: string; text?: string; mention?: PromptMention }
|
||||
|
||||
export type SessionMessageAssistantText = { type: "text"; text: string; state?: SessionMessageProviderState }
|
||||
export type SessionMessageAssistantText = { type: "text"; text: string; native?: SessionMessageProviderState }
|
||||
|
||||
export type SessionMessageAssistantReasoning = {
|
||||
type: "reasoning"
|
||||
text: string
|
||||
state?: SessionMessageProviderState
|
||||
native?: SessionMessageProviderState
|
||||
time?: { created: number; completed?: number }
|
||||
}
|
||||
|
||||
@@ -1266,7 +1266,7 @@ export type SessionStepEnded = {
|
||||
assistantMessageID: string
|
||||
finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
rawFinish?: string
|
||||
providerState?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
cost: MoneyUSD
|
||||
tokens: TokenUsageInfo
|
||||
snapshot?: string
|
||||
@@ -1287,7 +1287,7 @@ export type SessionStepFailed = {
|
||||
error: SessionStructuredError
|
||||
finish?: "content-filter"
|
||||
rawFinish?: string
|
||||
providerState?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
cost?: MoneyUSD
|
||||
tokens?: TokenUsageInfo
|
||||
snapshot?: string
|
||||
@@ -1307,7 +1307,7 @@ export type SessionTextEnded = {
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
state?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1318,7 +1318,7 @@ export type SessionReasoningStarted = {
|
||||
type: "session.reasoning.started"
|
||||
durable: { aggregateID: string; seq: number; version: 1 }
|
||||
location?: LocationRef
|
||||
data: { sessionID: string; assistantMessageID: string; ordinal: number; state?: SessionMessageProviderState1 }
|
||||
data: { sessionID: string; assistantMessageID: string; ordinal: number; native?: SessionMessageProviderState1 }
|
||||
}
|
||||
|
||||
export type SessionReasoningEnded = {
|
||||
@@ -1333,7 +1333,7 @@ export type SessionReasoningEnded = {
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
state?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1350,16 +1350,16 @@ export type SessionToolCalled = {
|
||||
id: string
|
||||
input: { [x: string]: any }
|
||||
executed: boolean
|
||||
state?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantText1 = { type: "text"; text: string; state?: SessionMessageProviderState1 }
|
||||
export type SessionMessageAssistantText1 = { type: "text"; text: string; native?: SessionMessageProviderState1 }
|
||||
|
||||
export type SessionMessageAssistantReasoning1 = {
|
||||
type: "reasoning"
|
||||
text: string
|
||||
state?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
time?: { created: number; completed?: number }
|
||||
}
|
||||
|
||||
@@ -1762,7 +1762,7 @@ export type SessionMessageCompactionCompleted = {
|
||||
status: "completed"
|
||||
reason: "auto" | "manual"
|
||||
model?: ModelRef
|
||||
providerState?: SessionMessageProviderState
|
||||
native?: SessionMessageProviderState
|
||||
summary: string
|
||||
recent: string
|
||||
providerContext?: SessionProviderContext
|
||||
@@ -1781,7 +1781,7 @@ export type SessionCompactionEnded = {
|
||||
sessionID: string
|
||||
reason: "auto" | "manual"
|
||||
model?: ModelRef
|
||||
providerState?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
providerContext?: SessionProviderContext
|
||||
text: string
|
||||
recent: string
|
||||
@@ -2169,7 +2169,7 @@ export type SessionMessageAssistantTool = {
|
||||
id: string
|
||||
name: string
|
||||
executed?: boolean
|
||||
providerState?: SessionMessageProviderState
|
||||
native?: SessionMessageProviderState
|
||||
providerResultState?: SessionMessageProviderState
|
||||
state:
|
||||
| SessionMessageToolStateStreaming
|
||||
@@ -2189,7 +2189,7 @@ export type SessionMessageAssistantTool1 = {
|
||||
id: string
|
||||
name: string
|
||||
executed?: boolean
|
||||
providerState?: SessionMessageProviderState1
|
||||
native?: SessionMessageProviderState1
|
||||
providerResultState?: SessionMessageProviderState1
|
||||
state:
|
||||
| SessionMessageToolStateStreaming
|
||||
@@ -2228,7 +2228,7 @@ export type SessionMessageAssistant = {
|
||||
snapshot?: { start?: string; end?: string; files?: Array<string> }
|
||||
finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
rawFinish?: string
|
||||
providerState?: SessionMessageProviderState
|
||||
native?: SessionMessageProviderState
|
||||
cost?: MoneyUSD
|
||||
tokens?: TokenUsageInfo
|
||||
error?: SessionStructuredError
|
||||
@@ -3102,11 +3102,11 @@ export type SessionImportInput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string; readonly state?: { readonly [x: string]: JsonValue } }
|
||||
| { readonly type: "text"; readonly text: string; readonly native?: { readonly [x: string]: JsonValue } }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly text: string
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
@@ -3114,7 +3114,7 @@ export type SessionImportInput = {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly executed?: boolean
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerResultState?: { readonly [x: string]: JsonValue }
|
||||
readonly state:
|
||||
| { readonly status: "streaming"; readonly input: string }
|
||||
@@ -3180,7 +3180,7 @@ export type SessionImportInput = {
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly rawFinish?: string
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3214,7 +3214,7 @@ export type SessionImportInput = {
|
||||
readonly status: "completed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly providerContext?: {
|
||||
@@ -3419,11 +3419,11 @@ export type SessionImportInput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string; readonly state?: { readonly [x: string]: JsonValue } }
|
||||
| { readonly type: "text"; readonly text: string; readonly native?: { readonly [x: string]: JsonValue } }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly text: string
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
@@ -3431,7 +3431,7 @@ export type SessionImportInput = {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly executed?: boolean
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerResultState?: { readonly [x: string]: JsonValue }
|
||||
readonly state:
|
||||
| { readonly status: "streaming"; readonly input: string }
|
||||
@@ -3497,7 +3497,7 @@ export type SessionImportInput = {
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly rawFinish?: string
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3531,7 +3531,7 @@ export type SessionImportInput = {
|
||||
readonly status: "completed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly providerContext?: {
|
||||
@@ -3736,11 +3736,11 @@ export type SessionImportInput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly text: string; readonly state?: { readonly [x: string]: JsonValue } }
|
||||
| { readonly type: "text"; readonly text: string; readonly native?: { readonly [x: string]: JsonValue } }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly text: string
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
@@ -3748,7 +3748,7 @@ export type SessionImportInput = {
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly executed?: boolean
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly providerResultState?: { readonly [x: string]: JsonValue }
|
||||
readonly state:
|
||||
| { readonly status: "streaming"; readonly input: string }
|
||||
@@ -3814,7 +3814,7 @@ export type SessionImportInput = {
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly rawFinish?: string
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3848,7 +3848,7 @@ export type SessionImportInput = {
|
||||
readonly status: "completed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly model?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly native?: { readonly [x: string]: JsonValue }
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
readonly providerContext?: {
|
||||
|
||||
@@ -846,7 +846,7 @@ export function createData(config: CreateDataInput) {
|
||||
existing.error = undefined
|
||||
existing.finish = undefined
|
||||
existing.rawFinish = undefined
|
||||
existing.providerState = undefined
|
||||
existing.native = undefined
|
||||
existing.time.created = event.data.started
|
||||
existing.time.streamed = undefined
|
||||
existing.time.completed = undefined
|
||||
@@ -880,7 +880,7 @@ export function createData(config: CreateDataInput) {
|
||||
assistant.time.completed = event.created
|
||||
assistant.finish = event.data.finish
|
||||
assistant.rawFinish = event.data.rawFinish
|
||||
assistant.providerState = event.data.providerState
|
||||
assistant.native = event.data.native
|
||||
assistant.cost = event.data.cost
|
||||
assistant.tokens = event.data.tokens
|
||||
if (event.data.snapshot) assistant.snapshot = { ...assistant.snapshot, end: event.data.snapshot }
|
||||
@@ -892,7 +892,7 @@ export function createData(config: CreateDataInput) {
|
||||
assistant.time.completed = event.created
|
||||
assistant.finish = event.data.finish ?? "error"
|
||||
assistant.rawFinish = event.data.rawFinish
|
||||
assistant.providerState = event.data.providerState
|
||||
assistant.native = event.data.native
|
||||
assistant.error = event.data.error
|
||||
assistant.retry = undefined
|
||||
if (event.data.cost !== undefined && event.data.tokens !== undefined) {
|
||||
@@ -914,6 +914,7 @@ export function createData(config: CreateDataInput) {
|
||||
case "session.text.ended":
|
||||
message.editText(event.data.sessionID, event.data.assistantMessageID, (text) => {
|
||||
text.text = event.data.text
|
||||
text.native = event.data.native
|
||||
})
|
||||
return
|
||||
case "session.tool.input.started":
|
||||
@@ -941,7 +942,7 @@ export function createData(config: CreateDataInput) {
|
||||
message.editTool(event.data.sessionID, event.data.assistantMessageID, event.data.id, (tool) => {
|
||||
tool.time.ran = event.created
|
||||
tool.executed = event.data.executed
|
||||
tool.providerState = event.data.state
|
||||
tool.native = event.data.native
|
||||
tool.state = { status: "running", input: event.data.input, metadata: {} }
|
||||
})
|
||||
return
|
||||
@@ -984,7 +985,7 @@ export function createData(config: CreateDataInput) {
|
||||
assistant.content.push({
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
state: event.data.state,
|
||||
native: event.data.native,
|
||||
time: { created: event.created },
|
||||
})
|
||||
})
|
||||
@@ -998,7 +999,7 @@ export function createData(config: CreateDataInput) {
|
||||
message.editReasoning(event.data.sessionID, event.data.assistantMessageID, (reasoning) => {
|
||||
reasoning.text = event.data.text
|
||||
reasoning.time = { created: reasoning.time?.created ?? event.created, completed: event.created }
|
||||
if (event.data.state !== undefined) reasoning.state = event.data.state
|
||||
if (event.data.native !== undefined) reasoning.native = event.data.native
|
||||
})
|
||||
return
|
||||
case "session.retry.scheduled":
|
||||
@@ -1105,7 +1106,7 @@ export function createData(config: CreateDataInput) {
|
||||
status: "completed",
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
providerState: event.data.providerState,
|
||||
native: event.data.native,
|
||||
providerContext: event.data.providerContext,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
@@ -1120,7 +1121,7 @@ export function createData(config: CreateDataInput) {
|
||||
status: "completed",
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
providerState: event.data.providerState,
|
||||
native: event.data.native,
|
||||
providerContext: event.data.providerContext,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
|
||||
@@ -120,7 +120,7 @@ test.each(["started", "cancelled", "failed"])(
|
||||
sessionID,
|
||||
reason: "manual",
|
||||
model,
|
||||
providerState,
|
||||
native: providerState,
|
||||
providerContext,
|
||||
text: "Summary",
|
||||
recent: "Recent",
|
||||
@@ -135,7 +135,7 @@ test.each(["started", "cancelled", "failed"])(
|
||||
status: "completed",
|
||||
summary: "Summary",
|
||||
model,
|
||||
providerState,
|
||||
native: providerState,
|
||||
providerContext,
|
||||
cost: 0.01,
|
||||
tokens,
|
||||
|
||||
@@ -379,13 +379,13 @@ export function transformSession(input: TransformInput): TransformResult {
|
||||
return []
|
||||
const content = owned.flatMap((part): Array<Record<string, unknown>> => {
|
||||
if (part.type === "text")
|
||||
return [{ type: "text", text: part.text, ...(part.metadata ? { state: part.metadata } : {}) }]
|
||||
return [{ type: "text", text: part.text, ...(part.metadata ? { native: part.metadata } : {}) }]
|
||||
if (part.type === "reasoning")
|
||||
return [
|
||||
{
|
||||
type: "reasoning",
|
||||
text: part.text,
|
||||
...(part.metadata ? { state: part.metadata } : {}),
|
||||
...(part.metadata ? { native: part.metadata } : {}),
|
||||
time: { created: part.time.start, ...(part.time.end === undefined ? {} : { completed: part.time.end }) },
|
||||
},
|
||||
]
|
||||
|
||||
@@ -447,7 +447,7 @@ export const layer = Layer.effect(
|
||||
sessionID: context.session.id,
|
||||
reason: input.reason,
|
||||
model: context.model.ref,
|
||||
providerState: result.providerState,
|
||||
native: result.native,
|
||||
text: result.summary,
|
||||
recent,
|
||||
...usage,
|
||||
@@ -607,7 +607,7 @@ export const layer = Layer.effect(
|
||||
const chunks: string[] = []
|
||||
let failure: SessionError.Error | undefined
|
||||
let usage: SessionUsage.Recorded | undefined
|
||||
let providerState: SessionMessage.ProviderState | undefined
|
||||
let native: SessionMessage.ProviderState | undefined
|
||||
const recordUsage = Effect.suspend(() =>
|
||||
usage
|
||||
? bus.publish(SessionEvent.UsageRecorded, {
|
||||
@@ -648,7 +648,7 @@ export const layer = Layer.effect(
|
||||
]) {
|
||||
yield* Stream.suspend(() => {
|
||||
chunks.length = 0
|
||||
providerState = undefined
|
||||
native = undefined
|
||||
failure = undefined
|
||||
return llm.stream(request, prepared.options)
|
||||
}).pipe(
|
||||
@@ -666,7 +666,7 @@ export const layer = Layer.effect(
|
||||
})
|
||||
}
|
||||
if (LLMEvent.is.stepFinish(event)) {
|
||||
providerState =
|
||||
native =
|
||||
event.providerMetadata?.[context.model.model.route.providerMetadataKey ?? context.model.model.provider]
|
||||
const step = SessionUsage.record(event.usage, context.model.cost)
|
||||
usage = usage ? SessionUsage.add(usage, step) : step
|
||||
@@ -726,7 +726,7 @@ export const layer = Layer.effect(
|
||||
sessionID: context.session.id,
|
||||
reason: input.reason,
|
||||
model: context.model.ref,
|
||||
providerState,
|
||||
native,
|
||||
text: summary,
|
||||
recent: history.recent,
|
||||
...usage,
|
||||
|
||||
@@ -60,7 +60,7 @@ export const latestCompaction = Effect.fnUntraced(function* (
|
||||
})
|
||||
|
||||
export const decodeMessageRow = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||
decode({ ...row.data, id: row.id, type: row.type }).pipe(
|
||||
decode(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type })).pipe(
|
||||
Effect.tap((message) =>
|
||||
SessionProviderContext.isCheckpoint(message)
|
||||
? SessionProviderContext.validate(message.providerContext)
|
||||
|
||||
@@ -125,7 +125,7 @@ const promotedFromMessage = Effect.fn("SessionInbox.promotedFromMessage")(functi
|
||||
if (row === undefined) return undefined
|
||||
if (row.session_id !== sessionID || (row.type !== "user" && row.type !== "synthetic"))
|
||||
return yield* new LifecycleConflict({ id })
|
||||
const message = decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
const message = decodeMessage(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type }))
|
||||
const base = { id, sessionID, time: { created: message.time.created }, delivery }
|
||||
if (message.type === "user")
|
||||
return User.make({
|
||||
|
||||
@@ -222,7 +222,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
draft.error = undefined
|
||||
draft.finish = undefined
|
||||
draft.rawFinish = undefined
|
||||
draft.providerState = undefined
|
||||
draft.native = undefined
|
||||
draft.time.created = DateTime.makeUnsafe(event.data.started)
|
||||
draft.time.streamed = undefined
|
||||
draft.time.completed = undefined
|
||||
@@ -263,7 +263,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
draft.time.completed = created
|
||||
draft.finish = event.data.finish
|
||||
draft.rawFinish = event.data.rawFinish
|
||||
draft.providerState = castDraft(event.data.providerState)
|
||||
draft.native = castDraft(event.data.native)
|
||||
draft.cost = event.data.cost
|
||||
draft.tokens = event.data.tokens
|
||||
projectTerminalSnapshot(draft, event)
|
||||
@@ -274,7 +274,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
draft.time.completed = created
|
||||
draft.finish = event.data.finish ?? "error"
|
||||
draft.rawFinish = event.data.rawFinish
|
||||
draft.providerState = castDraft(event.data.providerState)
|
||||
draft.native = castDraft(event.data.native)
|
||||
draft.error = castDraft(event.data.error)
|
||||
draft.retry = undefined
|
||||
if (event.data.cost !== undefined && event.data.tokens !== undefined) {
|
||||
@@ -294,7 +294,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
const match = latestText(draft)
|
||||
if (match) {
|
||||
match.text = event.data.text
|
||||
match.state = castDraft(event.data.state)
|
||||
match.native = castDraft(event.data.native)
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -324,7 +324,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
const match = latestTool(draft, event.data.id)
|
||||
if (match) {
|
||||
match.executed = event.data.executed
|
||||
match.providerState = event.data.state
|
||||
match.native = event.data.native
|
||||
match.time.ran = created
|
||||
match.state = castDraft(
|
||||
SessionMessage.ToolStateRunning.make({
|
||||
@@ -382,7 +382,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
state: event.data.state,
|
||||
native: event.data.native,
|
||||
time: { created },
|
||||
}),
|
||||
),
|
||||
@@ -395,7 +395,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
if (match) {
|
||||
match.text = event.data.text
|
||||
match.time = { created: match.time?.created ?? created, completed: created }
|
||||
if (event.data.state !== undefined) match.state = event.data.state
|
||||
if (event.data.native !== undefined) match.native = event.data.native
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -431,7 +431,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
metadata: event.metadata ? { ...current.metadata, ...event.metadata } : current.metadata,
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
providerState: event.data.providerState,
|
||||
native: event.data.native,
|
||||
summary: event.data.text,
|
||||
providerContext: event.data.providerContext,
|
||||
recent: event.data.recent,
|
||||
@@ -448,7 +448,7 @@ export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
|
||||
metadata: event.metadata,
|
||||
reason: event.data.reason,
|
||||
model: event.data.model,
|
||||
providerState: event.data.providerState,
|
||||
native: event.data.native,
|
||||
summary: event.data.text,
|
||||
providerContext: event.data.providerContext,
|
||||
recent: event.data.recent,
|
||||
|
||||
@@ -228,7 +228,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
||||
function run(db: DatabaseService, event: MessageEvent) {
|
||||
return Effect.gen(function* () {
|
||||
const decodeRow = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||
decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
decodeMessage(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type }))
|
||||
const updateMessage = (message: SessionMessage.Info) => {
|
||||
const encoded = encodeMessage(message)
|
||||
const { id, type, ...data } = encoded
|
||||
|
||||
@@ -107,7 +107,9 @@ const plan = Effect.fn("SessionRevert.plan")(function* (db: Database.Interface["
|
||||
const decode = Schema.decodeUnknownEffect(SessionMessage.Info)
|
||||
const files = new Map<RelativePath, Snapshot.ID>()
|
||||
for (const row of rows) {
|
||||
const message = yield* decode({ ...row.data, id: row.id, type: row.type }).pipe(Effect.orDie)
|
||||
const message = yield* decode(SessionMessage.persisted({ ...row.data, id: row.id, type: row.type })).pipe(
|
||||
Effect.orDie,
|
||||
)
|
||||
if (message.type !== "assistant" || !message.snapshot?.start) continue
|
||||
for (const file of message.snapshot.files ?? [])
|
||||
if (!files.has(file)) files.set(file, Snapshot.ID.make(message.snapshot.start))
|
||||
|
||||
@@ -38,7 +38,7 @@ export interface StepRecord {
|
||||
readonly finish?: {
|
||||
readonly finish: Extract<LLMEvent, { type: "step-finish" }>["reason"]["normalized"]
|
||||
readonly rawFinish?: string
|
||||
readonly providerState?: SessionMessage.ProviderState
|
||||
readonly native?: SessionMessage.ProviderState
|
||||
readonly tokens: ReturnType<typeof SessionUsage.tokens>
|
||||
}
|
||||
readonly needsContinuation: boolean
|
||||
@@ -205,7 +205,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
ordinal,
|
||||
text: value,
|
||||
state,
|
||||
native: state,
|
||||
})
|
||||
}),
|
||||
(_textID, value, ordinal) =>
|
||||
@@ -228,7 +228,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
ordinal,
|
||||
text: value,
|
||||
state,
|
||||
native: state,
|
||||
})
|
||||
}),
|
||||
(_reasoningID, value, ordinal) =>
|
||||
@@ -373,7 +373,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
error: stepFailure,
|
||||
finish: stepSettlement?.finish === "content-filter" ? stepSettlement.finish : undefined,
|
||||
rawFinish: stepSettlement?.rawFinish,
|
||||
providerState: stepSettlement?.providerState,
|
||||
native: stepSettlement?.native,
|
||||
...details,
|
||||
})
|
||||
})
|
||||
@@ -409,7 +409,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* startAssistant(),
|
||||
ordinal: startedReasoningOrdinal,
|
||||
state: providerState(event.providerMetadata),
|
||||
native: providerState(event.providerMetadata),
|
||||
})
|
||||
return
|
||||
case "reasoning-delta":
|
||||
@@ -453,7 +453,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
id: event.id,
|
||||
input: asRecord(event.input),
|
||||
executed: tool.providerExecuted,
|
||||
state: providerState(event.providerMetadata),
|
||||
native: providerState(event.providerMetadata),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -521,7 +521,7 @@ export const createLLMEventPublisher = (bus: Pick<Bus.Interface, "publish">, inp
|
||||
stepSettlement = {
|
||||
finish: event.reason.normalized,
|
||||
rawFinish: event.reason.raw,
|
||||
providerState: providerState(event.providerMetadata),
|
||||
native: providerState(event.providerMetadata),
|
||||
tokens: SessionUsage.tokens(event.usage),
|
||||
}
|
||||
if (event.reason.normalized === "content-filter") {
|
||||
|
||||
@@ -236,7 +236,7 @@ export const make = Effect.gen(function* () {
|
||||
assistantMessageID: yield* publisher.startAssistant(),
|
||||
finish: record.finish.finish,
|
||||
rawFinish: record.finish.rawFinish,
|
||||
providerState: record.finish.providerState,
|
||||
native: record.finish.native,
|
||||
...usage,
|
||||
snapshot,
|
||||
files,
|
||||
|
||||
@@ -162,7 +162,7 @@ const assistant = (message: SessionMessage.Assistant, model: Model.Ref, provider
|
||||
text: item.text,
|
||||
// Text can carry provider-bound state (e.g. Gemini thought signatures),
|
||||
// which is only replayable against the model that produced it.
|
||||
providerMetadata: reuseProviderMetadata ? providerMetadata(providerMetadataKey, item.state) : undefined,
|
||||
providerMetadata: reuseProviderMetadata ? providerMetadata(providerMetadataKey, item.native) : undefined,
|
||||
},
|
||||
]
|
||||
// Let the destination adapter handle readable reasoning after a model/provider switch.
|
||||
@@ -172,7 +172,7 @@ const assistant = (message: SessionMessage.Assistant, model: Model.Ref, provider
|
||||
{
|
||||
type: "reasoning",
|
||||
text: item.text,
|
||||
providerMetadata: providerMetadata(providerMetadataKey, item.state),
|
||||
providerMetadata: providerMetadata(providerMetadataKey, item.native),
|
||||
},
|
||||
]
|
||||
: item.text.length > 0
|
||||
@@ -186,19 +186,19 @@ const assistant = (message: SessionMessage.Assistant, model: Model.Ref, provider
|
||||
(sameModel && item.executed === true && (item.state.status === "completed" || item.state.status === "error"))
|
||||
const call = toolCall(
|
||||
item,
|
||||
reuseToolProviderMetadata ? providerMetadata(providerMetadataKey, item.providerState) : undefined,
|
||||
reuseToolProviderMetadata ? providerMetadata(providerMetadataKey, item.native) : undefined,
|
||||
)
|
||||
if (item.executed !== true) return [call]
|
||||
// Hosted tools (e.g. google_search) run inside the provider, so their
|
||||
// result payload (`providerResultState`) is provider-format data rather
|
||||
// than model-scoped proof: it stays replayable across models of the same
|
||||
// provider. After a model switch, echo only that payload — never fall
|
||||
// back to `providerState`, whose call-side values are bound to the old
|
||||
// back to `native`, whose call-side values are bound to the old
|
||||
// model.
|
||||
const result = toolResult(
|
||||
item,
|
||||
reuseToolProviderMetadata
|
||||
? providerMetadata(providerMetadataKey, item.providerResultState ?? item.providerState)
|
||||
? providerMetadata(providerMetadataKey, item.providerResultState ?? item.native)
|
||||
: sameProvider && item.providerResultState !== undefined
|
||||
? providerMetadata(providerMetadataKey, item.providerResultState)
|
||||
: undefined,
|
||||
@@ -216,7 +216,7 @@ const assistant = (message: SessionMessage.Assistant, model: Model.Ref, provider
|
||||
toolResult(
|
||||
item,
|
||||
reuseProviderMetadata
|
||||
? providerMetadata(providerMetadataKey, item.providerResultState ?? item.providerState)
|
||||
? providerMetadata(providerMetadataKey, item.providerResultState ?? item.native)
|
||||
: undefined,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -269,17 +269,17 @@ function sanitizeMessage(message: SessionMessage.Info): SessionMessage.Info {
|
||||
return {
|
||||
...content,
|
||||
text: redact("text", message.id, content.text),
|
||||
state: content.state ? { redacted: `text-state:${message.id}` } : undefined,
|
||||
native: content.native ? { redacted: `text-native:${message.id}` } : undefined,
|
||||
}
|
||||
if (content.type === "reasoning")
|
||||
return {
|
||||
...content,
|
||||
text: redact("reasoning", message.id, content.text),
|
||||
state: content.state ? { redacted: `reasoning-state:${message.id}` } : undefined,
|
||||
native: content.native ? { redacted: `reasoning-native:${message.id}` } : undefined,
|
||||
}
|
||||
return {
|
||||
...content,
|
||||
providerState: content.providerState ? { redacted: `tool-provider-state:${message.id}` } : undefined,
|
||||
native: content.native ? { redacted: `tool-native:${message.id}` } : undefined,
|
||||
providerResultState: content.providerResultState
|
||||
? { redacted: `tool-provider-result-state:${message.id}` }
|
||||
: undefined,
|
||||
@@ -299,7 +299,7 @@ function sanitizeMessage(message: SessionMessage.Info): SessionMessage.Info {
|
||||
summary: redact("compaction-summary", message.id, message.summary),
|
||||
recent: redact("compaction-recent", message.id, message.recent),
|
||||
...(message.status === "completed"
|
||||
? { providerState: metadata("compaction-provider-state", message.id, message.providerState) }
|
||||
? { native: metadata("compaction-native", message.id, message.native) }
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1272,7 +1272,7 @@ describe("SessionTransfer", () => {
|
||||
const runningCompactionID = SessionMessage.ID.create()
|
||||
const completedCompactionID = SessionMessage.ID.create()
|
||||
const model = Model.Ref.make({ id: Model.ID.make("model"), providerID: Provider.ID.make("provider") })
|
||||
const providerState = { responseId: "summary-response" }
|
||||
const native = { responseId: "summary-response" }
|
||||
|
||||
yield* transfer.import({
|
||||
data: {
|
||||
@@ -1328,7 +1328,7 @@ describe("SessionTransfer", () => {
|
||||
status: "completed",
|
||||
reason: "manual",
|
||||
model,
|
||||
providerState,
|
||||
native,
|
||||
summary: "summary",
|
||||
recent: "recent",
|
||||
time: { created: DateTime.makeUnsafe(9) },
|
||||
@@ -1345,10 +1345,10 @@ describe("SessionTransfer", () => {
|
||||
completedCompactionID,
|
||||
])
|
||||
expect(yield* Bus.latestSequence(db, sessionID)).toBe(4)
|
||||
expect((yield* transfer.export({ sessionID })).messages.at(-1)).toMatchObject({ model, providerState })
|
||||
expect((yield* transfer.export({ sessionID })).messages.at(-1)).toMatchObject({ model, native })
|
||||
expect((yield* transfer.export({ sessionID, sanitize: true })).messages.at(-1)).toMatchObject({
|
||||
model,
|
||||
providerState: { redacted: `compaction-provider-state:${completedCompactionID}` },
|
||||
native: { redacted: `compaction-native:${completedCompactionID}` },
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -413,7 +413,7 @@ it.live("compaction hooks supply the summary instead of provider compaction", ()
|
||||
Effect.sync(() => {
|
||||
event.result = {
|
||||
summary: "## Objective\n- hooked summary",
|
||||
providerState: { responseId: "plugin" },
|
||||
native: { responseId: "plugin" },
|
||||
metadata: { plugin: "custom" },
|
||||
tokens: { input: 10, output: 5, reasoning: 0, cache: { read: 0, write: 0 } },
|
||||
}
|
||||
@@ -426,7 +426,7 @@ it.live("compaction hooks supply the summary instead of provider compaction", ()
|
||||
status: "completed",
|
||||
summary: "## Objective\n- hooked summary",
|
||||
recent: "",
|
||||
providerState: { responseId: "plugin" },
|
||||
native: { responseId: "plugin" },
|
||||
metadata: { plugin: "custom" },
|
||||
tokens: { input: 10, output: 5 },
|
||||
})
|
||||
|
||||
@@ -659,7 +659,7 @@ describe("SessionProjector", () => {
|
||||
assistantMessageID: endedID,
|
||||
finish: "stop",
|
||||
rawFinish: "stop_sequence",
|
||||
providerState: { response: "ended" },
|
||||
native: { response: "ended" },
|
||||
cost: Money.USD.make(1),
|
||||
tokens: { input: 2, output: 3, reasoning: 4, cache: { read: 5, write: 6 } },
|
||||
snapshot: Snapshot.ID.make("snap_ended"),
|
||||
@@ -670,7 +670,7 @@ describe("SessionProjector", () => {
|
||||
assistantMessageID: failedID,
|
||||
finish: "content-filter",
|
||||
rawFinish: "blocked",
|
||||
providerState: { response: "failed" },
|
||||
native: { response: "failed" },
|
||||
error: { type: "provider.invalid-request", message: "Failed" },
|
||||
snapshot: Snapshot.ID.make("snap_failed"),
|
||||
files: [RelativePath.make("src/failed.ts")],
|
||||
@@ -690,7 +690,7 @@ describe("SessionProjector", () => {
|
||||
type: "assistant",
|
||||
finish: "stop",
|
||||
rawFinish: "stop_sequence",
|
||||
providerState: { response: "ended" },
|
||||
native: { response: "ended" },
|
||||
cost: Money.USD.make(1),
|
||||
tokens: { input: 2, output: 3, reasoning: 4, cache: { read: 5, write: 6 } },
|
||||
snapshot: { end: "snap_ended", files: ["src/ended.ts"] },
|
||||
@@ -700,7 +700,7 @@ describe("SessionProjector", () => {
|
||||
type: "assistant",
|
||||
finish: "content-filter",
|
||||
rawFinish: "blocked",
|
||||
providerState: { response: "failed" },
|
||||
native: { response: "failed" },
|
||||
error: { type: "provider.invalid-request", message: "Failed" },
|
||||
snapshot: { end: "snap_failed", files: ["src/failed.ts"] },
|
||||
time: { completed: created },
|
||||
|
||||
@@ -72,7 +72,7 @@ describe("toLLMMessages", () => {
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
state: { signature: "sig_1" },
|
||||
native: { signature: "sig_1" },
|
||||
}),
|
||||
]),
|
||||
],
|
||||
@@ -711,7 +711,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Think",
|
||||
state: { signature: "sig_1" },
|
||||
native: { signature: "sig_1" },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
@@ -755,7 +755,7 @@ Recent work
|
||||
id: "hosted",
|
||||
name: "web_search",
|
||||
executed: true,
|
||||
providerState: { continuation: "hosted-call" },
|
||||
native: { continuation: "hosted-call" },
|
||||
providerResultState: { continuation: "hosted-result" },
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
@@ -769,7 +769,7 @@ Recent work
|
||||
id: "hosted-failed",
|
||||
name: "write",
|
||||
executed: true,
|
||||
providerState: { continuation: "failed" },
|
||||
native: { continuation: "failed" },
|
||||
state: SessionMessage.ToolStateError.make({
|
||||
status: "error",
|
||||
input: { path: "README.md" },
|
||||
@@ -860,7 +860,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Think",
|
||||
state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
native: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
@@ -891,7 +891,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Think",
|
||||
state: { signature: "signed" },
|
||||
native: { signature: "signed" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
@@ -918,14 +918,14 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Partial thought",
|
||||
state: { itemId: "rs_failed", reasoningEncryptedContent: null },
|
||||
native: { itemId: "rs_failed", reasoningEncryptedContent: null },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "hosted-completed",
|
||||
name: "web_search",
|
||||
executed: true,
|
||||
providerState: { itemId: "call_completed" },
|
||||
native: { itemId: "call_completed" },
|
||||
providerResultState: { itemId: "result_completed" },
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
@@ -939,7 +939,7 @@ Recent work
|
||||
id: "hosted-failed",
|
||||
name: "web_search",
|
||||
executed: true,
|
||||
providerState: { itemId: "call_failed" },
|
||||
native: { itemId: "call_failed" },
|
||||
providerResultState: { itemId: "result_failed" },
|
||||
state: SessionMessage.ToolStateError.make({
|
||||
status: "error",
|
||||
@@ -1016,14 +1016,14 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Visible thought",
|
||||
state: { signature: "sig_old" },
|
||||
native: { signature: "sig_old" },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "hosted-old-model",
|
||||
name: "web_search",
|
||||
executed: true,
|
||||
providerState: { itemId: "hosted-old-model" },
|
||||
native: { itemId: "hosted-old-model" },
|
||||
providerResultState: { itemId: "hosted-old-model" },
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
@@ -1037,7 +1037,7 @@ Recent work
|
||||
id: "local-old-model",
|
||||
name: "read",
|
||||
executed: false,
|
||||
providerState: { call: "old" },
|
||||
native: { call: "old" },
|
||||
providerResultState: { result: "old" },
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
@@ -1110,7 +1110,7 @@ Recent work
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
text: "Visible thought",
|
||||
state: { reasoningEncryptedContent: "encrypted" },
|
||||
native: { reasoningEncryptedContent: "encrypted" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
@@ -1140,7 +1140,7 @@ Recent work
|
||||
SessionMessage.AssistantText.make({
|
||||
type: "text",
|
||||
text: "Checking.",
|
||||
state: { phase: "commentary" },
|
||||
native: { phase: "commentary" },
|
||||
}),
|
||||
],
|
||||
error: { type: "provider.unknown", message: "Interrupted after commentary" },
|
||||
@@ -1171,7 +1171,7 @@ Recent work
|
||||
SessionMessage.AssistantText.make({
|
||||
type: "text",
|
||||
text: "Checking.",
|
||||
state: { phase: "commentary" },
|
||||
native: { phase: "commentary" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
|
||||
@@ -299,7 +299,7 @@ test("provider metadata is flattened using the route key", async () => {
|
||||
)
|
||||
|
||||
expect(published.find((event) => event.type === "session.reasoning.started.1")?.data).toMatchObject({
|
||||
state: { signature: "signed" },
|
||||
native: { signature: "signed" },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -326,7 +326,7 @@ test("reasoning state from start, empty delta, and end is merged", async () => {
|
||||
)
|
||||
|
||||
expect(published.find((event) => event.type === "session.reasoning.ended.1")?.data).toMatchObject({
|
||||
state: { blockType: "thinking", signature: "signed", stopReason: "tool_use" },
|
||||
native: { blockType: "thinking", signature: "signed", stopReason: "tool_use" },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -508,7 +508,7 @@ test("provider-executed tool metadata is flattened using the route key", async (
|
||||
)
|
||||
|
||||
expect(published.find((event) => event.type === "session.tool.called.1")?.data).toMatchObject({
|
||||
state: { itemId: "call" },
|
||||
native: { itemId: "call" },
|
||||
})
|
||||
expect(published.find((event) => event.type === "session.tool.success.2")?.data).toMatchObject({
|
||||
resultState: { itemId: "result" },
|
||||
@@ -576,7 +576,7 @@ test("content-filter finish retains failure evidence until step closeout", async
|
||||
expect(settlement).toMatchObject({
|
||||
finish: "content-filter",
|
||||
rawFinish: "refusal",
|
||||
providerState: {
|
||||
native: {
|
||||
stopDetails: { type: "refusal", category: "safety", explanation: "Blocked" },
|
||||
},
|
||||
tokens: { input: 8, output: 2, reasoning: 1 },
|
||||
@@ -595,7 +595,7 @@ test("content-filter finish retains failure evidence until step closeout", async
|
||||
error: { type: "provider.content-filter", message: "Provider blocked the response" },
|
||||
finish: "content-filter",
|
||||
rawFinish: "refusal",
|
||||
providerState: {
|
||||
native: {
|
||||
stopDetails: { type: "refusal", category: "safety", explanation: "Blocked" },
|
||||
},
|
||||
cost: 1.25,
|
||||
|
||||
@@ -2558,7 +2558,7 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(s.executions).toEqual(["x".repeat(4_000)])
|
||||
expect((yield* s.messages).find((message) => message.type === "compaction")).toMatchObject({
|
||||
model: { id: s.currentModel.id, providerID: s.currentModel.provider, variant },
|
||||
providerState: { responseId: "summary" },
|
||||
native: { responseId: "summary" },
|
||||
})
|
||||
|
||||
// Compare wire content without the cache breakpoints that move to the new final message.
|
||||
@@ -3336,7 +3336,7 @@ describe("SessionRunnerLLM", () => {
|
||||
id: "call-provider",
|
||||
name: "web_search",
|
||||
executed: true,
|
||||
providerState: { source: "provider" },
|
||||
native: { source: "provider" },
|
||||
providerResultState: { source: "provider" },
|
||||
},
|
||||
{
|
||||
@@ -3508,12 +3508,12 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Signed thought",
|
||||
state: { signature: "sig_1" },
|
||||
native: { signature: "sig_1" },
|
||||
},
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Encrypted thought",
|
||||
state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
native: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
},
|
||||
]),
|
||||
])
|
||||
@@ -3565,7 +3565,7 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "thinking",
|
||||
state: { reasoningField: "reasoning", reasoningDetails: details },
|
||||
native: { reasoningField: "reasoning", reasoningDetails: details },
|
||||
},
|
||||
{ type: "text", text: "Hello world" },
|
||||
]),
|
||||
@@ -3609,7 +3609,7 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(yield* s.context).toMatchObject([
|
||||
Expected.user("Check first"),
|
||||
Expected.assistant({}, [
|
||||
{ type: "text", text: "Checking.", state: { itemId: "msg_commentary", phase: "commentary" } },
|
||||
{ type: "text", text: "Checking.", native: { itemId: "msg_commentary", phase: "commentary" } },
|
||||
]),
|
||||
])
|
||||
|
||||
@@ -4302,7 +4302,7 @@ describe("SessionRunnerLLM", () => {
|
||||
id: "call-hosted-interrupted",
|
||||
input: { query: "stale" },
|
||||
executed: true,
|
||||
state: { itemId: "call-hosted-interrupted" },
|
||||
native: { itemId: "call-hosted-interrupted" },
|
||||
})
|
||||
s.requests.length = 0
|
||||
yield* s.llm.push([])
|
||||
@@ -4966,7 +4966,7 @@ describe("SessionRunnerLLM", () => {
|
||||
type: "assistant",
|
||||
finish: "stop",
|
||||
rawFinish: "end_turn",
|
||||
providerState: { responseId: "response-1", serviceTier: "priority" },
|
||||
native: { responseId: "response-1", serviceTier: "priority" },
|
||||
content: [Expected.text("Complete")],
|
||||
},
|
||||
])
|
||||
@@ -4997,7 +4997,7 @@ describe("SessionRunnerLLM", () => {
|
||||
type: "assistant",
|
||||
finish: "content-filter",
|
||||
rawFinish: "SAFETY",
|
||||
providerState: {
|
||||
native: {
|
||||
responseId: "response-blocked",
|
||||
refusal: { category: "safety", explanation: "Prompt blocked" },
|
||||
},
|
||||
@@ -5444,7 +5444,7 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "",
|
||||
state: { itemId: "rs_disconnected", reasoningEncryptedContent: "encrypted-state" },
|
||||
native: { itemId: "rs_disconnected", reasoningEncryptedContent: "encrypted-state" },
|
||||
},
|
||||
]),
|
||||
{ type: "synthetic", text: INCOMPLETE_STREAM_CONTINUATION },
|
||||
|
||||
@@ -337,8 +337,8 @@ describe("V1Migration.transformSession", () => {
|
||||
agent: "build",
|
||||
model: { id: "model", providerID: "provider", variant: "fast" },
|
||||
content: [
|
||||
{ type: "text", text: "", state: { separator: true } },
|
||||
{ type: "reasoning", text: "think", state: { provider: 1 }, time: { created: 21, completed: 22 } },
|
||||
{ type: "text", text: "", native: { separator: true } },
|
||||
{ type: "reasoning", text: "think", native: { provider: 1 }, time: { created: 21, completed: 22 } },
|
||||
],
|
||||
snapshot: { start: "snap_start", end: "snap_end", files: ["a.ts", "b.ts", "c.ts"] },
|
||||
finish: "stop",
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface SessionContext extends SessionRequest {
|
||||
|
||||
export interface SessionCompactionResult {
|
||||
summary: string
|
||||
providerState?: SessionMessage.ProviderState
|
||||
native?: SessionMessage.ProviderState
|
||||
metadata?: Record<string, unknown>
|
||||
tokens?: TokenUsage.Info
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface SessionContext extends SessionRequest {
|
||||
|
||||
export interface SessionCompactionResult {
|
||||
summary: string
|
||||
providerState?: SessionMessage.ProviderState
|
||||
native?: SessionMessage.ProviderState
|
||||
metadata?: Record<string, unknown>
|
||||
tokens?: TokenUsage.Info
|
||||
}
|
||||
|
||||
@@ -17320,7 +17320,7 @@
|
||||
"rawFinish": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_4"
|
||||
},
|
||||
"cost": {
|
||||
@@ -17349,7 +17349,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_1"
|
||||
},
|
||||
"time": {
|
||||
@@ -17396,7 +17396,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState"
|
||||
}
|
||||
},
|
||||
@@ -17419,7 +17419,7 @@
|
||||
"executed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_2"
|
||||
},
|
||||
"providerResultState": {
|
||||
@@ -17509,7 +17509,7 @@
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Model.Ref"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_5"
|
||||
},
|
||||
"summary": {
|
||||
|
||||
@@ -353,7 +353,7 @@ export namespace Step {
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
finish: FinishReason,
|
||||
rawFinish: Schema.String.pipe(optional),
|
||||
providerState: SessionMessage.ProviderState.pipe(optional),
|
||||
native: SessionMessage.ProviderState.pipe(optional),
|
||||
cost: Money.USD,
|
||||
tokens: TokenUsage.Info,
|
||||
snapshot: Snapshot.ID.pipe(optional),
|
||||
@@ -371,7 +371,7 @@ export namespace Step {
|
||||
error: SessionError.Error,
|
||||
finish: Schema.Literals(["content-filter"]).pipe(optional),
|
||||
rawFinish: Schema.String.pipe(optional),
|
||||
providerState: SessionMessage.ProviderState.pipe(optional),
|
||||
native: SessionMessage.ProviderState.pipe(optional),
|
||||
cost: Money.USD.pipe(optional),
|
||||
tokens: TokenUsage.Info.pipe(optional),
|
||||
snapshot: Snapshot.ID.pipe(optional),
|
||||
@@ -413,7 +413,7 @@ export namespace Text {
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
ordinal: NonNegativeInt,
|
||||
text: Schema.String,
|
||||
state: SessionMessage.ProviderState.pipe(optional),
|
||||
native: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Ended = typeof Ended.Type
|
||||
@@ -427,7 +427,7 @@ export namespace Reasoning {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
ordinal: NonNegativeInt,
|
||||
state: SessionMessage.ProviderState.pipe(optional),
|
||||
native: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Started = typeof Started.Type
|
||||
@@ -452,7 +452,7 @@ export namespace Reasoning {
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
ordinal: NonNegativeInt,
|
||||
text: Schema.String,
|
||||
state: SessionMessage.ProviderState.pipe(optional),
|
||||
native: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Ended = typeof Ended.Type
|
||||
@@ -504,7 +504,7 @@ export namespace Tool {
|
||||
...ToolBase,
|
||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||
executed: Schema.Boolean,
|
||||
state: SessionMessage.ProviderState.pipe(optional),
|
||||
native: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Called = typeof Called.Type
|
||||
@@ -601,7 +601,7 @@ export namespace Compaction {
|
||||
...Base,
|
||||
reason: Started.data.fields.reason,
|
||||
model: SessionMessage.CompactionCompleted.fields.model,
|
||||
providerState: SessionMessage.CompactionCompleted.fields.providerState,
|
||||
native: SessionMessage.CompactionCompleted.fields.native,
|
||||
providerContext: SessionMessage.CompactionCompleted.fields.providerContext,
|
||||
text: Schema.String,
|
||||
recent: Schema.String,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export * as SessionMessage from "./session-message.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Predicate, Schema } from "effect"
|
||||
import { SessionProviderContext } from "./session-provider-context.js"
|
||||
import { optional } from "./schema.js"
|
||||
import { Content } from "./tool.js"
|
||||
@@ -163,7 +163,7 @@ export const AssistantTool = Schema.Struct({
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
executed: Schema.Boolean.pipe(optional),
|
||||
providerState: ProviderState.pipe(optional),
|
||||
native: ProviderState.pipe(optional),
|
||||
providerResultState: ProviderState.pipe(optional),
|
||||
state: ToolState,
|
||||
time: Schema.Struct({
|
||||
@@ -177,14 +177,14 @@ export interface AssistantText extends Schema.Schema.Type<typeof AssistantText>
|
||||
export const AssistantText = Schema.Struct({
|
||||
type: Schema.tag("text"),
|
||||
text: Schema.String,
|
||||
state: ProviderState.pipe(optional),
|
||||
native: ProviderState.pipe(optional),
|
||||
}).annotate({ identifier: "Session.Message.Assistant.Text" })
|
||||
|
||||
export interface AssistantReasoning extends Schema.Schema.Type<typeof AssistantReasoning> {}
|
||||
export const AssistantReasoning = Schema.Struct({
|
||||
type: Schema.tag("reasoning"),
|
||||
text: Schema.String,
|
||||
state: ProviderState.pipe(optional),
|
||||
native: ProviderState.pipe(optional),
|
||||
time: Schema.Struct({
|
||||
created: DateTimeUtcFromMillis,
|
||||
completed: DateTimeUtcFromMillis.pipe(optional),
|
||||
@@ -222,7 +222,7 @@ export const Assistant = Schema.Struct({
|
||||
}).pipe(optional),
|
||||
finish: FinishReason.pipe(optional),
|
||||
rawFinish: Schema.String.pipe(optional),
|
||||
providerState: ProviderState.pipe(optional),
|
||||
native: ProviderState.pipe(optional),
|
||||
cost: Money.USD.pipe(optional),
|
||||
tokens: TokenUsage.Info.pipe(optional),
|
||||
error: SessionError.Error.pipe(optional),
|
||||
@@ -258,7 +258,7 @@ export const CompactionCompleted = Schema.Struct({
|
||||
status: Schema.tag("completed"),
|
||||
reason: Schema.Literals(["auto", "manual"]),
|
||||
model: Model.Ref.pipe(optional),
|
||||
providerState: ProviderState.pipe(optional),
|
||||
native: ProviderState.pipe(optional),
|
||||
summary: Schema.String,
|
||||
recent: Schema.String,
|
||||
providerContext: SessionProviderContext.Info.pipe(optional),
|
||||
@@ -318,3 +318,26 @@ export type Info =
|
||||
| Compaction
|
||||
| Idle
|
||||
export type Type = Info["type"]
|
||||
|
||||
/** Reads messages stored before provider blobs were renamed to `native`. */
|
||||
export function persisted(input: unknown) {
|
||||
if (!Predicate.isObject(input)) return input
|
||||
const message =
|
||||
input.type === "assistant" || input.type === "compaction" ? rename(input, "providerState", "native") : input
|
||||
if (message.type !== "assistant" || !Array.isArray(message.content)) return message
|
||||
const stored: ReadonlyArray<unknown> = message.content
|
||||
const content = stored.map((part) => {
|
||||
if (!Predicate.isObject(part)) return part
|
||||
if (part.type === "text" || part.type === "reasoning") return rename(part, "state", "native")
|
||||
if (part.type === "tool") return rename(part, "providerState", "native")
|
||||
return part
|
||||
})
|
||||
return content.every((part, index) => part === stored[index]) ? message : { ...message, content }
|
||||
}
|
||||
|
||||
function rename(record: Record<string, unknown>, from: string, to: string) {
|
||||
if (record[from] === undefined || record[to] !== undefined) return record
|
||||
const value = record[from]
|
||||
const rest = Object.fromEntries(Object.entries(record).filter(([key]) => key !== from))
|
||||
return { ...rest, [to]: value }
|
||||
}
|
||||
|
||||
@@ -257,15 +257,15 @@ describe("contract hygiene", () => {
|
||||
text: "hello",
|
||||
})
|
||||
expect(
|
||||
SessionMessage.AssistantReasoning.make({ type: "reasoning", text: "thinking", state: { id: "opaque" } }),
|
||||
).toEqual({ type: "reasoning", text: "thinking", state: { id: "opaque" } })
|
||||
SessionMessage.AssistantReasoning.make({ type: "reasoning", text: "thinking", native: { id: "opaque" } }),
|
||||
).toEqual({ type: "reasoning", text: "thinking", native: { id: "opaque" } })
|
||||
expect(
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "call_1",
|
||||
name: "search",
|
||||
executed: true,
|
||||
providerState: { itemId: "item_1" },
|
||||
native: { itemId: "item_1" },
|
||||
state: { status: "streaming", input: "" },
|
||||
time: { created: DateTime.makeUnsafe(0) },
|
||||
}),
|
||||
|
||||
@@ -190,7 +190,7 @@ describe("public event manifest", () => {
|
||||
assistantMessageID,
|
||||
ordinal: 0,
|
||||
text: "thought",
|
||||
state: { signature: "sig" },
|
||||
native: { signature: "sig" },
|
||||
})
|
||||
const tool = SessionEvent.Tool.Called.data.make({
|
||||
sessionID,
|
||||
@@ -198,7 +198,7 @@ describe("public event manifest", () => {
|
||||
id: "call_test",
|
||||
input: {},
|
||||
executed: true,
|
||||
state: { itemId: "item_test" },
|
||||
native: { itemId: "item_test" },
|
||||
})
|
||||
|
||||
expect(text).not.toHaveProperty("textID")
|
||||
|
||||
@@ -23,50 +23,23 @@ test("assistant terminal diagnostics remain optional and round trip", () => {
|
||||
...assistant,
|
||||
finish: "content-filter",
|
||||
rawFinish: "SAFETY",
|
||||
providerState: { promptFeedback: { blockReason: "SAFETY" } },
|
||||
native: { promptFeedback: { blockReason: "SAFETY" } },
|
||||
}),
|
||||
),
|
||||
).toMatchObject({
|
||||
finish: "content-filter",
|
||||
rawFinish: "SAFETY",
|
||||
native: { promptFeedback: { blockReason: "SAFETY" } },
|
||||
})
|
||||
const legacy = SessionMessage.persisted({
|
||||
...assistant,
|
||||
providerState: { promptFeedback: { blockReason: "SAFETY" } },
|
||||
content: [{ type: "text", text: "hello", state: { signature: "sig" } }],
|
||||
})
|
||||
})
|
||||
|
||||
test("failed steps only override the assistant finish for content filters", () => {
|
||||
const decode = Schema.decodeUnknownSync(SessionEvent.Step.Failed.data)
|
||||
const input = {
|
||||
sessionID: "ses_terminal",
|
||||
assistantMessageID: "msg_terminal",
|
||||
error: { type: "provider.content-filter", message: "Blocked" },
|
||||
}
|
||||
|
||||
expect(decode(input)).toMatchObject(input)
|
||||
expect(decode({ ...input, finish: "content-filter", rawFinish: "SAFETY" })).toMatchObject({
|
||||
finish: "content-filter",
|
||||
rawFinish: "SAFETY",
|
||||
expect(decode(legacy)).toMatchObject({
|
||||
native: { promptFeedback: { blockReason: "SAFETY" } },
|
||||
content: [{ type: "text", native: { signature: "sig" } }],
|
||||
})
|
||||
expect(() => decode({ ...input, finish: "stop" })).toThrow()
|
||||
})
|
||||
|
||||
test("provider compaction context is optional, versioned and JSON-only", () => {
|
||||
const decode = Schema.decodeUnknownSync(SessionEvent.Compaction.Ended.data)
|
||||
const encode = Schema.encodeSync(SessionEvent.Compaction.Ended.data)
|
||||
const local = { sessionID: "ses_context", reason: "manual" as const, text: "summary", recent: "" }
|
||||
expect(encode({ ...decode(local), providerContext: undefined })).toEqual(local)
|
||||
const providerContext = {
|
||||
version: 1 as const,
|
||||
provenance: {
|
||||
providerID: "openai",
|
||||
provider: "openai",
|
||||
modelID: "deployment",
|
||||
route: "responses",
|
||||
protocol: "responses",
|
||||
endpoint: "digest",
|
||||
},
|
||||
messages: [{ role: "assistant", content: [{ type: "compaction", provider: "openai", encrypted: "opaque" }] }],
|
||||
}
|
||||
expect(encode(decode({ ...local, providerContext }))).toEqual({ ...local, providerContext })
|
||||
expect(() => decode({ ...local, providerContext: { ...providerContext, version: 2 } })).toThrow()
|
||||
expect(() => decode({ ...local, providerContext: { ...providerContext, messages: [() => "invalid"] } })).toThrow()
|
||||
expect(encode(decode(legacy))).not.toHaveProperty("providerState")
|
||||
expect(SessionMessage.persisted(assistant)).toBe(assistant)
|
||||
})
|
||||
|
||||
@@ -190,13 +190,13 @@ export const streamingDocument = document(
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "## Checking the current contract\n\nThe assistant content is nested on each current Session message.",
|
||||
state: { phase: "streaming" },
|
||||
native: { phase: "streaming" },
|
||||
time: { created: STORY_TIME + 11_100 },
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
text: "I have the typed rows in place. Next I am checking the streaming presentation",
|
||||
state: { phase: "streaming" },
|
||||
native: { phase: "streaming" },
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -47,7 +47,7 @@ function MermaidTimeline(props: { streaming: boolean }) {
|
||||
"```mermaid\nsequenceDiagram\n Client->>Server: Send prompt\n Server->>Model: Generate response\n Model-->>Client: Response\n" +
|
||||
(completed() ? "```" : ""),
|
||||
].join("\n\n"),
|
||||
...(completed() ? {} : { state: { phase: "streaming" } }),
|
||||
...(completed() ? {} : { native: { phase: "streaming" } }),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -800,7 +800,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
id: event.data.id,
|
||||
name: current?.part.name ?? "tool",
|
||||
executed: event.data.executed,
|
||||
providerState: event.data.state,
|
||||
native: event.data.native,
|
||||
state: { status: "running", input: event.data.input, metadata: {} },
|
||||
time: { created: current?.part.time.created ?? event.created, ran: event.created },
|
||||
},
|
||||
@@ -822,7 +822,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
id: event.data.id,
|
||||
name: part?.name ?? "tool",
|
||||
executed: part?.executed,
|
||||
providerState: part?.providerState,
|
||||
native: part?.native,
|
||||
state: {
|
||||
status: "running",
|
||||
input: part && part.state.status !== "streaming" ? part.state.input : {},
|
||||
@@ -852,7 +852,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
id: event.data.id,
|
||||
name: part?.name ?? "tool",
|
||||
executed: event.data.executed,
|
||||
providerState: part?.providerState,
|
||||
native: part?.native,
|
||||
providerResultState: event.data.resultState,
|
||||
state: failed
|
||||
? {
|
||||
|
||||
@@ -1279,7 +1279,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
id: event.data.id,
|
||||
name: current?.part.name ?? "tool",
|
||||
executed: event.data.executed,
|
||||
providerState: event.data.state,
|
||||
native: event.data.native,
|
||||
state: { status: "running", input: event.data.input, metadata: {} },
|
||||
time: { created: current?.part.time.created ?? event.created, ran: event.created },
|
||||
}
|
||||
@@ -1296,7 +1296,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
id: event.data.id,
|
||||
name: part?.name ?? "tool",
|
||||
executed: part?.executed,
|
||||
providerState: part?.providerState,
|
||||
native: part?.native,
|
||||
state: {
|
||||
status: "running",
|
||||
input: part && part.state.status !== "streaming" ? part.state.input : {},
|
||||
@@ -1315,7 +1315,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
id: event.data.id,
|
||||
name: part?.name ?? "tool",
|
||||
executed: event.data.executed,
|
||||
providerState: part?.providerState,
|
||||
native: part?.native,
|
||||
providerResultState: event.data.resultState,
|
||||
state: failed
|
||||
? {
|
||||
|
||||
@@ -2792,7 +2792,7 @@ test("settles pending tools when a live failure arrives", async () => {
|
||||
id: "call-1",
|
||||
input: {},
|
||||
executed: false,
|
||||
state: { call: true },
|
||||
native: { call: true },
|
||||
},
|
||||
})
|
||||
emitEvent(events, {
|
||||
@@ -2855,7 +2855,7 @@ test("settles pending tools when a live failure arrives", async () => {
|
||||
expect(tool.state.metadata).toBeUndefined()
|
||||
expect(tool.state.content).toBeUndefined()
|
||||
expect(tool.executed).toBe(false)
|
||||
expect(tool.providerState).toEqual({ call: true })
|
||||
expect(tool.native).toEqual({ call: true })
|
||||
expect(tool.providerResultState).toEqual({ result: true })
|
||||
expect(sync.session.message.list("session-1").map((message) => message.type)).toEqual([
|
||||
"agent-switched",
|
||||
|
||||
@@ -17320,7 +17320,7 @@
|
||||
"rawFinish": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_4"
|
||||
},
|
||||
"cost": {
|
||||
@@ -17349,7 +17349,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_1"
|
||||
},
|
||||
"time": {
|
||||
@@ -17396,7 +17396,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState"
|
||||
}
|
||||
},
|
||||
@@ -17419,7 +17419,7 @@
|
||||
"executed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_2"
|
||||
},
|
||||
"providerResultState": {
|
||||
@@ -17509,7 +17509,7 @@
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Model.Ref"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_5"
|
||||
},
|
||||
"summary": {
|
||||
|
||||
@@ -17320,7 +17320,7 @@
|
||||
"rawFinish": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_4"
|
||||
},
|
||||
"cost": {
|
||||
@@ -17349,7 +17349,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_1"
|
||||
},
|
||||
"time": {
|
||||
@@ -17396,7 +17396,7 @@
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState"
|
||||
}
|
||||
},
|
||||
@@ -17419,7 +17419,7 @@
|
||||
"executed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_2"
|
||||
},
|
||||
"providerResultState": {
|
||||
@@ -17509,7 +17509,7 @@
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Model.Ref"
|
||||
},
|
||||
"providerState": {
|
||||
"native": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_5"
|
||||
},
|
||||
"summary": {
|
||||
|
||||
@@ -1566,7 +1566,7 @@ interface SessionContextHook extends SessionRequestHook {
|
||||
|
||||
interface SessionCompactionResult {
|
||||
summary: string
|
||||
providerState?: Record<string, unknown>
|
||||
native?: Record<string, unknown>
|
||||
metadata?: Record<string, unknown>
|
||||
tokens?: TokenUsage
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user