feat(core): finalize session event lifecycle (#35272)
This commit is contained in:
parent
b046bbe4e3
commit
bf01264661
82 changed files with 5200 additions and 3711 deletions
|
|
@ -20,6 +20,7 @@ export { Revert } from "./revert.js"
|
|||
export { Session } from "./session.js"
|
||||
export { Vcs } from "./vcs.js"
|
||||
export { SessionInput } from "./session-input.js"
|
||||
export { SessionError } from "./session-error.js"
|
||||
export { SessionMessage } from "./session-message.js"
|
||||
export { Shell } from "./shell.js"
|
||||
export { Skill } from "./skill.js"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ export const ProviderMetadata = Schema.Record(Schema.String, Schema.Record(Schem
|
|||
})
|
||||
export type ProviderMetadata = Schema.Schema.Type<typeof ProviderMetadata>
|
||||
|
||||
export const FinishReason = Schema.Literals(["stop", "length", "tool-calls", "content-filter", "error", "unknown"])
|
||||
export type FinishReason = typeof FinishReason.Type
|
||||
|
||||
export interface ToolTextContent extends Schema.Schema.Type<typeof ToolTextContent> {}
|
||||
export const ToolTextContent = Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
|
|
|
|||
9
packages/schema/src/session-error.ts
Normal file
9
packages/schema/src/session-error.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export * as SessionError from "./session-error.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
|
||||
export interface Error extends Schema.Schema.Type<typeof Error> {}
|
||||
export const Error = Schema.Struct({
|
||||
type: Schema.String,
|
||||
message: Schema.String,
|
||||
}).annotate({ identifier: "Session.StructuredError" })
|
||||
|
|
@ -3,16 +3,18 @@ export * as SessionEvent from "./session-event.js"
|
|||
import { Schema } from "effect"
|
||||
import { optional } from "./schema.js"
|
||||
import { Event } from "./event.js"
|
||||
import { ProviderMetadata, ToolContent } from "./llm.js"
|
||||
import { ToolContent } from "./llm.js"
|
||||
import { FinishReason } from "./llm.js"
|
||||
import { Delivery } from "./session-delivery.js"
|
||||
import { Model } from "./model.js"
|
||||
import { NonNegativeInt, RelativePath } from "./schema.js"
|
||||
import { NonNegativeInt, PositiveInt, RelativePath } from "./schema.js"
|
||||
import { FileAttachment, Prompt } from "./prompt.js"
|
||||
import { SessionID } from "./session-id.js"
|
||||
import { Location } from "./location.js"
|
||||
import { SessionMessage } from "./session-message.js"
|
||||
import { Revert } from "./revert.js"
|
||||
import { Shell as ShellSchema } from "./shell.js"
|
||||
import { SessionError } from "./session-error.js"
|
||||
|
||||
export { FileAttachment }
|
||||
|
||||
|
|
@ -41,16 +43,6 @@ const options = {
|
|||
version: 1,
|
||||
},
|
||||
} as const
|
||||
const stepSettlementOptions = {
|
||||
durable: {
|
||||
aggregate: "sessionID",
|
||||
version: 1,
|
||||
},
|
||||
} as const
|
||||
|
||||
export const UnknownError = SessionMessage.UnknownError
|
||||
export type UnknownError = SessionMessage.UnknownError
|
||||
|
||||
export const AgentSelected = Event.durable({
|
||||
type: "session.agent.selected",
|
||||
...options,
|
||||
|
|
@ -120,15 +112,27 @@ export const PromptAdmitted = Event.durable({
|
|||
})
|
||||
export type PromptAdmitted = typeof PromptAdmitted.Type
|
||||
|
||||
export const ExecutionSettled = Event.ephemeral({
|
||||
type: "session.execution.settled",
|
||||
schema: {
|
||||
...Base,
|
||||
outcome: Schema.Literals(["success", "failure", "interrupted"]),
|
||||
error: UnknownError.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type ExecutionSettled = typeof ExecutionSettled.Type
|
||||
export namespace Execution {
|
||||
export const Started = Event.durable({ type: "session.execution.started", ...options, schema: Base })
|
||||
export type Started = typeof Started.Type
|
||||
|
||||
export const Succeeded = Event.durable({ type: "session.execution.succeeded", ...options, schema: Base })
|
||||
export type Succeeded = typeof Succeeded.Type
|
||||
|
||||
export const Failed = Event.durable({
|
||||
type: "session.execution.failed",
|
||||
...options,
|
||||
schema: { ...Base, error: SessionError.Error },
|
||||
})
|
||||
export type Failed = typeof Failed.Type
|
||||
|
||||
export const Interrupted = Event.durable({
|
||||
type: "session.execution.interrupted",
|
||||
...options,
|
||||
schema: { ...Base, reason: Schema.Literals(["user", "shutdown", "superseded"]) },
|
||||
})
|
||||
export type Interrupted = typeof Interrupted.Type
|
||||
}
|
||||
|
||||
export const InstructionsUpdated = Event.durable({
|
||||
type: "session.instructions.updated",
|
||||
|
|
@ -204,11 +208,11 @@ export namespace Step {
|
|||
|
||||
export const Ended = Event.durable({
|
||||
type: "session.step.ended",
|
||||
...stepSettlementOptions,
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
finish: Schema.String,
|
||||
finish: FinishReason,
|
||||
cost: Schema.Finite,
|
||||
tokens: Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
|
|
@ -227,11 +231,11 @@ export namespace Step {
|
|||
|
||||
export const Failed = Event.durable({
|
||||
type: "session.step.failed",
|
||||
...stepSettlementOptions,
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
error: UnknownError,
|
||||
error: SessionError.Error,
|
||||
},
|
||||
})
|
||||
export type Failed = typeof Failed.Type
|
||||
|
|
@ -244,7 +248,7 @@ export namespace Text {
|
|||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
textID: Schema.String,
|
||||
ordinal: NonNegativeInt,
|
||||
},
|
||||
})
|
||||
export type Started = typeof Started.Type
|
||||
|
|
@ -255,7 +259,7 @@ export namespace Text {
|
|||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
textID: Schema.String,
|
||||
ordinal: NonNegativeInt,
|
||||
delta: Schema.String,
|
||||
},
|
||||
})
|
||||
|
|
@ -267,7 +271,7 @@ export namespace Text {
|
|||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
textID: Schema.String,
|
||||
ordinal: NonNegativeInt,
|
||||
text: Schema.String,
|
||||
},
|
||||
})
|
||||
|
|
@ -281,8 +285,8 @@ export namespace Reasoning {
|
|||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
reasoningID: Schema.String,
|
||||
providerMetadata: ProviderMetadata.pipe(optional),
|
||||
ordinal: NonNegativeInt,
|
||||
state: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Started = typeof Started.Type
|
||||
|
|
@ -293,7 +297,7 @@ export namespace Reasoning {
|
|||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
reasoningID: Schema.String,
|
||||
ordinal: NonNegativeInt,
|
||||
delta: Schema.String,
|
||||
},
|
||||
})
|
||||
|
|
@ -305,9 +309,9 @@ export namespace Reasoning {
|
|||
schema: {
|
||||
...Base,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
reasoningID: Schema.String,
|
||||
ordinal: NonNegativeInt,
|
||||
text: Schema.String,
|
||||
providerMetadata: ProviderMetadata.pipe(optional),
|
||||
state: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Ended = typeof Ended.Type
|
||||
|
|
@ -357,12 +361,9 @@ export namespace Tool {
|
|||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
tool: Schema.String,
|
||||
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||
provider: Schema.Struct({
|
||||
executed: Schema.Boolean,
|
||||
metadata: ProviderMetadata.pipe(optional),
|
||||
}),
|
||||
executed: Schema.Boolean,
|
||||
state: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Called = typeof Called.Type
|
||||
|
|
@ -391,10 +392,8 @@ export namespace Tool {
|
|||
content: Schema.Array(ToolContent),
|
||||
outputPaths: Schema.Array(Schema.String).pipe(optional),
|
||||
result: Schema.Unknown.pipe(optional),
|
||||
provider: Schema.Struct({
|
||||
executed: Schema.Boolean,
|
||||
metadata: ProviderMetadata.pipe(optional),
|
||||
}),
|
||||
executed: Schema.Boolean,
|
||||
resultState: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Success = typeof Success.Type
|
||||
|
|
@ -404,39 +403,27 @@ export namespace Tool {
|
|||
...options,
|
||||
schema: {
|
||||
...ToolBase,
|
||||
error: UnknownError,
|
||||
error: SessionError.Error,
|
||||
result: Schema.Unknown.pipe(optional),
|
||||
provider: Schema.Struct({
|
||||
executed: Schema.Boolean,
|
||||
metadata: ProviderMetadata.pipe(optional),
|
||||
}),
|
||||
executed: Schema.Boolean,
|
||||
resultState: SessionMessage.ProviderState.pipe(optional),
|
||||
},
|
||||
})
|
||||
export type Failed = typeof Failed.Type
|
||||
}
|
||||
|
||||
export const RetryError = Schema.Struct({
|
||||
message: Schema.String,
|
||||
statusCode: Schema.Finite.pipe(optional),
|
||||
isRetryable: Schema.Boolean,
|
||||
responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
responseBody: Schema.String.pipe(optional),
|
||||
metadata: Schema.Record(Schema.String, Schema.String).pipe(optional),
|
||||
}).annotate({
|
||||
identifier: "session.retry.error",
|
||||
})
|
||||
export interface RetryError extends Schema.Schema.Type<typeof RetryError> {}
|
||||
|
||||
export const Retried = Event.durable({
|
||||
type: "session.retried",
|
||||
export const RetryScheduled = Event.durable({
|
||||
type: "session.retry.scheduled",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
attempt: Schema.Finite,
|
||||
error: RetryError,
|
||||
assistantMessageID: SessionMessage.ID,
|
||||
attempt: PositiveInt,
|
||||
at: NonNegativeInt,
|
||||
error: SessionError.Error,
|
||||
},
|
||||
})
|
||||
export type Retried = typeof Retried.Type
|
||||
export type RetryScheduled = typeof RetryScheduled.Type
|
||||
|
||||
export namespace Compaction {
|
||||
export const Started = Event.durable({
|
||||
|
|
@ -481,7 +468,7 @@ export namespace RevertEvent {
|
|||
export const Committed = Event.durable({
|
||||
type: "session.revert.committed",
|
||||
...options,
|
||||
schema: { ...Base, messageID: SessionMessage.ID },
|
||||
schema: { ...Base, to: SessionMessage.ID },
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -493,7 +480,10 @@ export const Definitions = Event.inventory(
|
|||
Forked,
|
||||
PromptPromoted,
|
||||
PromptAdmitted,
|
||||
ExecutionSettled,
|
||||
Execution.Started,
|
||||
Execution.Succeeded,
|
||||
Execution.Failed,
|
||||
Execution.Interrupted,
|
||||
InstructionsUpdated,
|
||||
Synthetic,
|
||||
Skill.Activated,
|
||||
|
|
@ -515,7 +505,7 @@ export const Definitions = Event.inventory(
|
|||
Tool.Progress,
|
||||
Tool.Success,
|
||||
Tool.Failed,
|
||||
Retried,
|
||||
RetryScheduled,
|
||||
Compaction.Started,
|
||||
Compaction.Delta,
|
||||
Compaction.Ended,
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ export * as SessionMessage from "./session-message.js"
|
|||
|
||||
import { Schema } from "effect"
|
||||
import { optional } from "./schema.js"
|
||||
import { ProviderMetadata, ToolContent } from "./llm.js"
|
||||
import { ToolContent } from "./llm.js"
|
||||
import { Model } from "./model.js"
|
||||
import { FileAttachment, Prompt } from "./prompt.js"
|
||||
import { DateTimeUtcFromMillis, RelativePath, statics } from "./schema.js"
|
||||
import { DateTimeUtcFromMillis, PositiveInt, RelativePath, statics } from "./schema.js"
|
||||
import { SessionID } from "./session-id.js"
|
||||
import { ascending } from "./identifier.js"
|
||||
import { Event } from "./event.js"
|
||||
import { Shell as ShellSchema } from "./shell.js"
|
||||
import { FinishReason } from "./llm.js"
|
||||
import { SessionError } from "./session-error.js"
|
||||
|
||||
export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
|
||||
Schema.brand("Session.Message.ID"),
|
||||
|
|
@ -20,18 +22,17 @@ export const ID = Schema.String.check(Schema.isStartsWith("msg_")).pipe(
|
|||
)
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export interface UnknownError extends Schema.Schema.Type<typeof UnknownError> {}
|
||||
export const UnknownError = Schema.Struct({
|
||||
type: Schema.Literal("unknown"),
|
||||
message: Schema.String,
|
||||
}).annotate({ identifier: "Session.Error.Unknown" })
|
||||
|
||||
const Base = {
|
||||
id: ID,
|
||||
metadata: Schema.Record(Schema.String, Schema.Unknown).pipe(optional),
|
||||
time: Schema.Struct({ created: DateTimeUtcFromMillis }),
|
||||
}
|
||||
|
||||
export const ProviderState = Schema.Record(Schema.String, Schema.Unknown).annotate({
|
||||
identifier: "Session.Message.ProviderState",
|
||||
})
|
||||
export type ProviderState = typeof ProviderState.Type
|
||||
|
||||
export interface AgentSelected extends Schema.Schema.Type<typeof AgentSelected> {}
|
||||
export const AgentSelected = Schema.Struct({
|
||||
...Base,
|
||||
|
|
@ -123,7 +124,7 @@ export const ToolStateError = Schema.Struct({
|
|||
input: Schema.Record(Schema.String, Schema.Unknown),
|
||||
content: ToolContent.pipe(Schema.Array),
|
||||
structured: Schema.Record(Schema.String, Schema.Unknown),
|
||||
error: UnknownError,
|
||||
error: SessionError.Error,
|
||||
result: Schema.Unknown.pipe(optional),
|
||||
}).annotate({ identifier: "Session.Message.ToolState.Error" })
|
||||
|
||||
|
|
@ -137,11 +138,9 @@ export const AssistantTool = Schema.Struct({
|
|||
type: Schema.Literal("tool"),
|
||||
id: Schema.String,
|
||||
name: Schema.String,
|
||||
provider: Schema.Struct({
|
||||
executed: Schema.Boolean,
|
||||
metadata: ProviderMetadata.pipe(optional),
|
||||
resultMetadata: ProviderMetadata.pipe(optional),
|
||||
}).pipe(optional),
|
||||
executed: Schema.Boolean.pipe(optional),
|
||||
providerState: ProviderState.pipe(optional),
|
||||
providerResultState: ProviderState.pipe(optional),
|
||||
state: ToolState,
|
||||
time: Schema.Struct({
|
||||
created: DateTimeUtcFromMillis,
|
||||
|
|
@ -154,16 +153,14 @@ export const AssistantTool = Schema.Struct({
|
|||
export interface AssistantText extends Schema.Schema.Type<typeof AssistantText> {}
|
||||
export const AssistantText = Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
id: Schema.String,
|
||||
text: Schema.String,
|
||||
}).annotate({ identifier: "Session.Message.Assistant.Text" })
|
||||
|
||||
export interface AssistantReasoning extends Schema.Schema.Type<typeof AssistantReasoning> {}
|
||||
export const AssistantReasoning = Schema.Struct({
|
||||
type: Schema.Literal("reasoning"),
|
||||
id: Schema.String,
|
||||
text: Schema.String,
|
||||
providerMetadata: ProviderMetadata.pipe(optional),
|
||||
state: ProviderState.pipe(optional),
|
||||
time: Schema.Struct({
|
||||
created: DateTimeUtcFromMillis,
|
||||
completed: DateTimeUtcFromMillis.pipe(optional),
|
||||
|
|
@ -175,6 +172,13 @@ export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning,
|
|||
)
|
||||
export type AssistantContent = AssistantText | AssistantReasoning | AssistantTool
|
||||
|
||||
export interface AssistantRetry extends Schema.Schema.Type<typeof AssistantRetry> {}
|
||||
export const AssistantRetry = Schema.Struct({
|
||||
attempt: PositiveInt,
|
||||
at: DateTimeUtcFromMillis,
|
||||
error: SessionError.Error,
|
||||
}).annotate({ identifier: "Session.Message.Assistant.Retry" })
|
||||
|
||||
export interface Assistant extends Schema.Schema.Type<typeof Assistant> {}
|
||||
export const Assistant = Schema.Struct({
|
||||
...Base,
|
||||
|
|
@ -187,7 +191,7 @@ export const Assistant = Schema.Struct({
|
|||
end: Schema.String.pipe(optional),
|
||||
files: Schema.Array(RelativePath).pipe(optional),
|
||||
}).pipe(optional),
|
||||
finish: Schema.String.pipe(optional),
|
||||
finish: FinishReason.pipe(optional),
|
||||
cost: Schema.Finite.pipe(optional),
|
||||
tokens: Schema.Struct({
|
||||
input: Schema.Finite,
|
||||
|
|
@ -195,7 +199,8 @@ export const Assistant = Schema.Struct({
|
|||
reasoning: Schema.Finite,
|
||||
cache: Schema.Struct({ read: Schema.Finite, write: Schema.Finite }),
|
||||
}).pipe(optional),
|
||||
error: UnknownError.pipe(optional),
|
||||
error: SessionError.Error.pipe(optional),
|
||||
retry: AssistantRetry.pipe(optional),
|
||||
time: Schema.Struct({
|
||||
created: DateTimeUtcFromMillis,
|
||||
completed: DateTimeUtcFromMillis.pipe(optional),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { DateTime, Schema } from "effect"
|
||||
import { Agent } from "../src/agent.js"
|
||||
import { FileSystem } from "../src/filesystem.js"
|
||||
import { Model } from "../src/model.js"
|
||||
|
|
@ -8,6 +8,7 @@ import { Provider } from "../src/provider.js"
|
|||
import { Pty } from "../src/pty.js"
|
||||
import { Question } from "../src/question.js"
|
||||
import { Session } from "../src/session.js"
|
||||
import { SessionMessage } from "../src/session-message.js"
|
||||
import { SessionTodo } from "../src/session-todo.js"
|
||||
import { optional } from "../src/schema.js"
|
||||
|
||||
|
|
@ -81,4 +82,25 @@ describe("contract hygiene", () => {
|
|||
expect(source).not.toContain("Schema.Any")
|
||||
expect(source).not.toContain("Schema.mutable")
|
||||
})
|
||||
|
||||
test("assistant content keeps only domain identities", () => {
|
||||
expect(SessionMessage.AssistantText.make({ type: "text", text: "hello" })).toEqual({
|
||||
type: "text",
|
||||
text: "hello",
|
||||
})
|
||||
expect(
|
||||
SessionMessage.AssistantReasoning.make({ type: "reasoning", text: "thinking", state: { id: "opaque" } }),
|
||||
).toEqual({ type: "reasoning", text: "thinking", state: { id: "opaque" } })
|
||||
expect(
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "call_1",
|
||||
name: "search",
|
||||
executed: true,
|
||||
providerState: { itemId: "item_1" },
|
||||
state: { status: "pending", input: "" },
|
||||
time: { created: DateTime.makeUnsafe(0) },
|
||||
}),
|
||||
).not.toHaveProperty("provider")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import { IdeEvent } from "../src/ide-event.js"
|
|||
import { McpEvent } from "../src/mcp-event.js"
|
||||
import { Plugin } from "../src/plugin.js"
|
||||
import { SessionEvent } from "../src/session-event.js"
|
||||
import { SessionID } from "../src/session-id.js"
|
||||
import { SessionMessage } from "../src/session-message.js"
|
||||
import { SessionTodo } from "../src/session-todo.js"
|
||||
import { SessionV1 } from "../src/session-v1.js"
|
||||
import { WorkspaceEvent } from "../src/workspace-event.js"
|
||||
|
|
@ -104,7 +106,10 @@ describe("public event manifest", () => {
|
|||
"session.forked.1",
|
||||
"session.prompt.promoted.1",
|
||||
"session.prompt.admitted.1",
|
||||
|
||||
"session.execution.started.1",
|
||||
"session.execution.succeeded.1",
|
||||
"session.execution.failed.1",
|
||||
"session.execution.interrupted.1",
|
||||
"session.instructions.updated.1",
|
||||
"session.synthetic.1",
|
||||
"session.skill.activated.1",
|
||||
|
|
@ -123,7 +128,7 @@ describe("public event manifest", () => {
|
|||
"session.tool.failed.1",
|
||||
"session.reasoning.started.1",
|
||||
"session.reasoning.ended.1",
|
||||
"session.retried.1",
|
||||
"session.retry.scheduled.1",
|
||||
"session.compaction.started.1",
|
||||
"session.compaction.ended.1",
|
||||
"session.revert.staged.1",
|
||||
|
|
@ -136,4 +141,33 @@ describe("public event manifest", () => {
|
|||
)
|
||||
expect(EventManifest.Definitions.every((definition) => definition.durability !== undefined)).toBe(true)
|
||||
})
|
||||
|
||||
test("keeps simplified session fragment and tool payloads on durable version 1", () => {
|
||||
const sessionID = SessionID.make("ses_test")
|
||||
const assistantMessageID = SessionMessage.ID.make("msg_test")
|
||||
const text = SessionEvent.Text.Started.data.make({ sessionID, assistantMessageID, ordinal: 0 })
|
||||
const reasoning = SessionEvent.Reasoning.Ended.data.make({
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
ordinal: 0,
|
||||
text: "thought",
|
||||
state: { signature: "sig" },
|
||||
})
|
||||
const tool = SessionEvent.Tool.Called.data.make({
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
callID: "call_test",
|
||||
input: {},
|
||||
executed: true,
|
||||
state: { itemId: "item_test" },
|
||||
})
|
||||
|
||||
expect(text).not.toHaveProperty("textID")
|
||||
expect(reasoning).not.toHaveProperty("reasoningID")
|
||||
expect(reasoning).not.toHaveProperty("providerMetadata")
|
||||
expect(tool).not.toHaveProperty("tool")
|
||||
expect(tool).not.toHaveProperty("provider")
|
||||
expect(SessionEvent.Text.Started.durable?.version).toBe(1)
|
||||
expect(SessionEvent.Tool.Called.durable?.version).toBe(1)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
46
packages/schema/test/session-error.test.ts
Normal file
46
packages/schema/test/session-error.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { LLM, SessionError } from "../src/index.js"
|
||||
|
||||
describe("SessionError", () => {
|
||||
test("exports one identified open envelope", () => {
|
||||
expect(SessionError.Error.ast.annotations?.identifier).toBe("Session.StructuredError")
|
||||
expect(Object.keys(SessionError).filter((key) => key !== "SessionError")).toEqual(["Error"])
|
||||
})
|
||||
|
||||
test("round trips current and future error types through JSON", () => {
|
||||
const values: SessionError.Error[] = [
|
||||
{ type: "provider.rate-limit", message: "Slow down" },
|
||||
{ type: "provider.auth", message: "Authentication failed" },
|
||||
{ type: "provider.future-condition", message: "A future provider failure" },
|
||||
{ type: "unknown", message: "Unexpected" },
|
||||
]
|
||||
const codec = Schema.fromJsonString(SessionError.Error)
|
||||
|
||||
for (const value of values) {
|
||||
const encoded = Schema.encodeSync(codec)(value)
|
||||
expect(Schema.decodeUnknownSync(codec)(encoded)).toEqual(value)
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts future fields while exposing only the stable envelope", () => {
|
||||
expect(
|
||||
Schema.decodeUnknownSync(SessionError.Error)({
|
||||
type: "provider.timeout",
|
||||
message: "Timeout",
|
||||
retryAfterMs: 2_500,
|
||||
}),
|
||||
).toEqual({ type: "provider.timeout", message: "Timeout" })
|
||||
})
|
||||
|
||||
test("rejects missing envelope fields", () => {
|
||||
expect(() => Schema.decodeUnknownSync(SessionError.Error)({ type: "provider.auth" })).toThrow()
|
||||
expect(() => Schema.decodeUnknownSync(SessionError.Error)({ message: "Missing type" })).toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
test("FinishReason is the closed browser-safe provider set", () => {
|
||||
const reasons = ["stop", "length", "tool-calls", "content-filter", "error", "unknown"] as const
|
||||
expect(reasons.map((reason) => Schema.decodeUnknownSync(LLM.FinishReason)(reason))).toEqual([...reasons])
|
||||
expect(() => Schema.decodeUnknownSync(LLM.FinishReason)("other")).toThrow()
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue