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
|
|
@ -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