Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41ffe60dab | |||
| beecb243bc | |||
| 69d95f29d4 | |||
| 7e2011dffb | |||
| 918005849b | |||
| 28b70d7b38 | |||
| a49e6c37b2 | |||
| 77e1702ea2 | |||
| fc20cd1e84 | |||
| c0758b0e90 | |||
| 7ef42ac296 | |||
| 8f4b62eb49 | |||
| 945d1c8cb2 | |||
| 610e618bc5 | |||
| 9daa4d85a4 | |||
| 62af66a74f | |||
| 5b44e5bf41 | |||
| a15afbe8f2 | |||
| 8e0856c43b | |||
| f66c829231 |
@@ -1,36 +0,0 @@
|
||||
export default {
|
||||
id: "Orchestrator",
|
||||
setup: async (ctx) => {
|
||||
await ctx.agent.transform((agents) => {
|
||||
agents.update("orchestrator", (agent) => {
|
||||
agent.description = "Coordinates work by delegating implementation tasks to the minion subagent."
|
||||
agent.mode = "primary"
|
||||
agent.system = [
|
||||
"You are Orchestrator, the primary coordinating agent for this repository. You do meta work only: you coordinate, brief, and synthesize — you do not perform the work itself.",
|
||||
"Delegate ALL actual work to the minion subagent — implementation, exploration, discovery, searching the codebase, reading files to understand a problem, and even trivial one-line edits. Task size is never a reason to do it yourself, and there is no 'final integration' exception.",
|
||||
"You are not hard-banned from tools, but direct tool use is reserved for coordination overhead: a quick peek to phrase a better brief, a fast read-only check to verify a minion's reported result, or answering a question about coordination state. If a tool call is producing the answer or the artifact the user asked for, that call belongs to a minion, not you.",
|
||||
"Exploration is work. If the user asks how something works or where something lives, delegate the investigation to a minion rather than exploring yourself.",
|
||||
"Always start minion subagents in the background. Even if you have nothing else to coordinate right now, the user may assign you new work while a Minion runs, and you must stay free to receive it. Never poll; you will be notified when they finish.",
|
||||
"Give each minion a clear, self-contained brief: the goal, constraints, expected output, and any files or context already known from the user or previous minion reports.",
|
||||
"Synthesize minion results, decide next steps, and report back concisely.",
|
||||
].join("\n")
|
||||
})
|
||||
|
||||
agents.update("minion", (agent) => {
|
||||
agent.description = "Subagent that executes focused tasks delegated by Orchestrator."
|
||||
agent.mode = "subagent"
|
||||
agent.model = { providerID: "opencode", id: "glm-5.2" }
|
||||
agent.system = [
|
||||
"You are minion, a focused execution subagent for this repository.",
|
||||
"Complete the specific task delegated to you by Orchestrator using the available tools.",
|
||||
"Inspect the codebase before making assumptions, make targeted changes when requested, and verify your work when feasible.",
|
||||
"Follow the repository's AGENTS.md conventions: respect the style guide, run `bun typecheck` from the affected package directory after code changes, never run tests from the repo root, and do not modify packages/opencode unless the task explicitly says V1 work.",
|
||||
"If the task is ambiguous or you hit a blocker, stop and report your findings instead of guessing.",
|
||||
"Keep your final response concise: summarize what you did, list important files changed or findings, and call out blockers or verification gaps.",
|
||||
"Do not delegate to other subagents; execute the assigned work yourself.",
|
||||
].join("\n")
|
||||
agent.permissions.push({ action: "subagent", resource: "*", effect: "deny" })
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -104,6 +104,25 @@ bun dev api <operationId> --param key=value
|
||||
- If no compatible background server is registered, `bun dev api` starts one through the daemon service. Use `bun dev service status`, `bun dev service restart`, and `bun dev service stop` when you need explicit lifecycle control.
|
||||
- Prefer raw method/path calls for quick server debugging and operation IDs when exercising documented OpenAPI routes with path or query parameters.
|
||||
|
||||
## Auditing installed `opencode2` sessions
|
||||
|
||||
Installed next-channel sessions normally use `~/.local/share/opencode/opencode-next.db` and `~/.local/share/opencode/log/opencode.log`; `OPENCODE_DB` can override the database. Before calling `opencode2 api`, inspect `~/.local/state/opencode/service.json` because the command may start a daemon when none is healthy.
|
||||
|
||||
For a supplied `ses_...` ID, compare three sources:
|
||||
|
||||
- `opencode2 api get /api/session/active` and the Session/message endpoints for live server state.
|
||||
- The database's ordered `event` rows for durable history.
|
||||
- `packages/tui/src/context/data.tsx` and the relevant route for client projection and rendering.
|
||||
|
||||
Locate an uncertain database without modifying it:
|
||||
|
||||
```bash
|
||||
SESSION=ses_...
|
||||
for db in ~/.local/share/opencode/*.db; do
|
||||
sqlite3 "file:$db?mode=ro" "select 1 from session where id='$SESSION' limit 1" 2>/dev/null | grep -q 1 && printf '%s\n' "$db"
|
||||
done
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
- Log files live under `~/.local/share/opencode/log/`. In a local/dev checkout the active file is `opencode-local.log`; `opencode.log` is used for non-local (released) channel installs. Both are append-only, shared across every CLI and server process on the machine.
|
||||
|
||||
@@ -151,6 +151,7 @@ const table = sqliteTable("session", {
|
||||
|
||||
## V2 Session Core
|
||||
|
||||
- Keep durable events minimal: record irreducible new facts and do not repeat state derivable by folding the ordered aggregate history. Enrich projections and read models with previous or derived state when consumers need self-contained views.
|
||||
- Keep durable prompt admission separate from model execution. `SessionV2.prompt(...)` admits one durable `session_input` row before scheduling advisory `SessionExecution.wake(sessionID)` unless `resume: false` requests admit-only behavior. The serialized runner promotes admitted inputs into visible user messages at safe boundaries.
|
||||
- Reusing a Session ID adopts the existing Session. Reusing a prompt message ID reconciles an exact retry only when Session, prompt, and delivery mode match; conflicting reuse fails. Historical projected prompts lazily synthesize promoted inbox records during exact retry.
|
||||
- Keep `SessionExecution` process-global and Session-ID based. Its local implementation owns the process-local Session coordinator and discovers placement through `SessionStore` plus `LocationServiceMap.get(session.location)` only when a drain starts; no layer should take a Session ID. V2 interruption targets the active process-local ownership chain for that Session; idle or missing interruption is a no-op.
|
||||
|
||||
@@ -163,8 +163,11 @@ export type Endpoint4_12Output = EffectValue<ReturnType<RawClient["server.sessio
|
||||
export type SessionShellOperation<E = never> = (input: Endpoint4_12Input) => Effect.Effect<Endpoint4_12Output, E>
|
||||
|
||||
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||
export type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
|
||||
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>
|
||||
export type Endpoint4_13Input = {
|
||||
readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint4_13Request["payload"]["id"]
|
||||
}
|
||||
export type Endpoint4_13Output = EffectValue<ReturnType<RawClient["server.session"]["session.compact"]>>["data"]
|
||||
export type SessionCompactOperation<E = never> = (input: Endpoint4_13Input) => Effect.Effect<Endpoint4_13Output, E>
|
||||
|
||||
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
||||
|
||||
@@ -221,9 +221,15 @@ const Endpoint4_12 = (raw: RawClient["server.session"]) => (input: Endpoint4_12I
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
type Endpoint4_13Request = Parameters<RawClient["server.session"]["session.compact"]>[0]
|
||||
type Endpoint4_13Input = { readonly sessionID: Endpoint4_13Request["params"]["sessionID"] }
|
||||
type Endpoint4_13Input = {
|
||||
readonly sessionID: Endpoint4_13Request["params"]["sessionID"]
|
||||
readonly id?: Endpoint4_13Request["payload"]["id"]
|
||||
}
|
||||
const Endpoint4_13 = (raw: RawClient["server.session"]) => (input: Endpoint4_13Input) =>
|
||||
raw["session.compact"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError))
|
||||
raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
)
|
||||
|
||||
type Endpoint4_14Request = Parameters<RawClient["server.session"]["session.wait"]>[0]
|
||||
type Endpoint4_14Input = { readonly sessionID: Endpoint4_14Request["params"]["sessionID"] }
|
||||
|
||||
@@ -540,16 +540,17 @@ export function make(options: ClientOptions) {
|
||||
requestOptions,
|
||||
),
|
||||
compact: (input: SessionCompactInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionCompactOutput>(
|
||||
request<{ readonly data: SessionCompactOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`,
|
||||
successStatus: 204,
|
||||
declaredStatuses: [404, 409, 503, 500, 400, 401],
|
||||
empty: true,
|
||||
body: { id: input["id"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [409, 404, 400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
).then((value) => value.data),
|
||||
wait: (input: SessionWaitInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionWaitOutput>(
|
||||
{
|
||||
|
||||
@@ -74,14 +74,6 @@ export type SkillNotFoundError = {
|
||||
export const isSkillNotFoundError = (value: unknown): value is SkillNotFoundError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SkillNotFoundError"
|
||||
|
||||
export type SessionBusyError = {
|
||||
readonly _tag: "SessionBusyError"
|
||||
readonly sessionID: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isSessionBusyError = (value: unknown): value is SessionBusyError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionBusyError"
|
||||
|
||||
export type ServiceUnavailableError = {
|
||||
readonly _tag: "ServiceUnavailableError"
|
||||
readonly message: string
|
||||
@@ -90,6 +82,14 @@ export type ServiceUnavailableError = {
|
||||
export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError"
|
||||
|
||||
export type SessionBusyError = {
|
||||
readonly _tag: "SessionBusyError"
|
||||
readonly sessionID: string
|
||||
readonly message: string
|
||||
}
|
||||
export const isSessionBusyError = (value: unknown): value is SessionBusyError =>
|
||||
typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SessionBusyError"
|
||||
|
||||
export type UnknownError = {
|
||||
readonly _tag: "UnknownError"
|
||||
readonly message: string
|
||||
@@ -873,9 +873,21 @@ export type SessionShellInput = {
|
||||
|
||||
export type SessionShellOutput = void
|
||||
|
||||
export type SessionCompactInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
export type SessionCompactInput = {
|
||||
readonly sessionID: { readonly sessionID: string }["sessionID"]
|
||||
readonly id?: { readonly id?: string | undefined }["id"]
|
||||
}
|
||||
|
||||
export type SessionCompactOutput = void
|
||||
export type SessionCompactOutput = {
|
||||
readonly data: {
|
||||
readonly type: "compaction"
|
||||
readonly admittedSeq: number
|
||||
readonly id: string
|
||||
readonly sessionID: string
|
||||
readonly timeCreated: number
|
||||
readonly handledSeq?: number
|
||||
}
|
||||
}["data"]
|
||||
|
||||
export type SessionWaitInput = { readonly sessionID: { readonly sessionID: string }["sessionID"] }
|
||||
|
||||
@@ -928,6 +940,7 @@ export type SessionContextOutput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "model-switched"
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly previous?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
@@ -1006,23 +1019,20 @@ export type SessionContextOutput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly id: string; readonly text: string }
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly id: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "tool"
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly provider?: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
readonly executed?: boolean
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly providerResultState?: { readonly [x: string]: JsonValue }
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: string }
|
||||
| {
|
||||
@@ -1060,7 +1070,7 @@ export type SessionContextOutput = {
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
readonly time: {
|
||||
@@ -1072,7 +1082,7 @@ export type SessionContextOutput = {
|
||||
}
|
||||
>
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: string
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -1080,10 +1090,16 @@ export type SessionContextOutput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error?: { readonly type: string; readonly message: string }
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly type: "compaction"
|
||||
readonly status: "queued" | "running" | "completed" | "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
@@ -1211,6 +1227,45 @@ export type SessionLogOutput =
|
||||
readonly delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.started"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.succeeded"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.failed"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.interrupted"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly reason: "user" | "shutdown" | "superseded" }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
@@ -1320,7 +1375,7 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly finish: string
|
||||
readonly finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly cost: number
|
||||
readonly tokens: {
|
||||
readonly input: number
|
||||
@@ -1342,7 +1397,7 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1352,7 +1407,7 @@ export type SessionLogOutput =
|
||||
readonly type: "session.text.started"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
|
||||
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly ordinal: number }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
@@ -1364,7 +1419,7 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
readonly ordinal: number
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
@@ -1378,8 +1433,8 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
readonly ordinal: number
|
||||
readonly state?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1392,9 +1447,9 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly ordinal: number
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
readonly state?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1436,12 +1491,9 @@ export type SessionLogOutput =
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly tool: string
|
||||
readonly input: { readonly [x: string]: unknown }
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
readonly executed: boolean
|
||||
readonly state?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1480,10 +1532,8 @@ export type SessionLogOutput =
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly result?: unknown
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
readonly executed: boolean
|
||||
readonly resultState?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1497,34 +1547,36 @@ export type SessionLogOutput =
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
readonly result?: unknown
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
readonly executed: boolean
|
||||
readonly resultState?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.retried"
|
||||
readonly type: "session.retry.scheduled"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly attempt: number
|
||||
readonly error: {
|
||||
readonly message: string
|
||||
readonly statusCode?: number
|
||||
readonly isRetryable: boolean
|
||||
readonly responseHeaders?: { readonly [x: string]: string }
|
||||
readonly responseBody?: string
|
||||
readonly metadata?: { readonly [x: string]: string }
|
||||
}
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.compaction.admitted"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly inputID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
@@ -1548,6 +1600,15 @@ export type SessionLogOutput =
|
||||
readonly recent: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.compaction.failed"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
@@ -1588,7 +1649,7 @@ export type SessionLogOutput =
|
||||
readonly type: "session.revert.committed"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly messageID: string }
|
||||
readonly data: { readonly sessionID: string; readonly to: string }
|
||||
}
|
||||
)
|
||||
| { readonly type: "log.synced"; readonly aggregateID: string; readonly seq?: number }
|
||||
@@ -1621,6 +1682,7 @@ export type SessionMessageOutput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "model-switched"
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly previous?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
@@ -1699,23 +1761,20 @@ export type SessionMessageOutput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly id: string; readonly text: string }
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly id: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "tool"
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly provider?: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
readonly executed?: boolean
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly providerResultState?: { readonly [x: string]: JsonValue }
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: string }
|
||||
| {
|
||||
@@ -1753,7 +1812,7 @@ export type SessionMessageOutput = {
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
readonly time: {
|
||||
@@ -1765,7 +1824,7 @@ export type SessionMessageOutput = {
|
||||
}
|
||||
>
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: string
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -1773,10 +1832,16 @@ export type SessionMessageOutput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error?: { readonly type: string; readonly message: string }
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly type: "compaction"
|
||||
readonly status: "queued" | "running" | "completed" | "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
@@ -1820,6 +1885,7 @@ export type MessageListOutput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly type: "model-switched"
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly previous?: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
@@ -1898,23 +1964,20 @@ export type MessageListOutput = {
|
||||
readonly agent: string
|
||||
readonly model: { readonly id: string; readonly providerID: string; readonly variant?: string }
|
||||
readonly content: ReadonlyArray<
|
||||
| { readonly type: "text"; readonly id: string; readonly text: string }
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
| {
|
||||
readonly type: "reasoning"
|
||||
readonly id: string
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly state?: { readonly [x: string]: JsonValue }
|
||||
readonly time?: { readonly created: number; readonly completed?: number }
|
||||
}
|
||||
| {
|
||||
readonly type: "tool"
|
||||
readonly id: string
|
||||
readonly name: string
|
||||
readonly provider?: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
readonly resultMetadata?: { readonly [x: string]: { readonly [x: string]: JsonValue } }
|
||||
}
|
||||
readonly executed?: boolean
|
||||
readonly providerState?: { readonly [x: string]: JsonValue }
|
||||
readonly providerResultState?: { readonly [x: string]: JsonValue }
|
||||
readonly state:
|
||||
| { readonly status: "pending"; readonly input: string }
|
||||
| {
|
||||
@@ -1952,7 +2015,7 @@ export type MessageListOutput = {
|
||||
| { readonly type: "file"; readonly uri: string; readonly mime: string; readonly name?: string }
|
||||
>
|
||||
readonly structured: { readonly [x: string]: JsonValue }
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
readonly result?: JsonValue
|
||||
}
|
||||
readonly time: {
|
||||
@@ -1964,7 +2027,7 @@ export type MessageListOutput = {
|
||||
}
|
||||
>
|
||||
readonly snapshot?: { readonly start?: string; readonly end?: string; readonly files?: ReadonlyArray<string> }
|
||||
readonly finish?: string
|
||||
readonly finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -1972,10 +2035,16 @@ export type MessageListOutput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error?: { readonly type: string; readonly message: string }
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly type: "compaction"
|
||||
readonly status: "queued" | "running" | "completed" | "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly summary: string
|
||||
readonly recent: string
|
||||
@@ -4485,13 +4554,37 @@ export type EventSubscribeOutput =
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.settled"
|
||||
readonly type: "session.execution.started"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly outcome: "success" | "failure" | "interrupted"
|
||||
readonly error?: { readonly type: "unknown"; readonly message: string }
|
||||
}
|
||||
readonly data: { readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.succeeded"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.failed"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly error: { readonly type: string; readonly message: string } }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.execution.interrupted"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly reason: "user" | "shutdown" | "superseded" }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
@@ -4602,7 +4695,7 @@ export type EventSubscribeOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly finish: string
|
||||
readonly finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
readonly cost: number
|
||||
readonly tokens: {
|
||||
readonly input: number
|
||||
@@ -4624,7 +4717,7 @@ export type EventSubscribeOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -4634,7 +4727,7 @@ export type EventSubscribeOutput =
|
||||
readonly type: "session.text.started"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly textID: string }
|
||||
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly ordinal: number }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
@@ -4645,7 +4738,7 @@ export type EventSubscribeOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
readonly ordinal: number
|
||||
readonly delta: string
|
||||
}
|
||||
}
|
||||
@@ -4659,7 +4752,7 @@ export type EventSubscribeOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly textID: string
|
||||
readonly ordinal: number
|
||||
readonly text: string
|
||||
}
|
||||
}
|
||||
@@ -4673,8 +4766,8 @@ export type EventSubscribeOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
readonly ordinal: number
|
||||
readonly state?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -4686,7 +4779,7 @@ export type EventSubscribeOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly ordinal: number
|
||||
readonly delta: string
|
||||
}
|
||||
}
|
||||
@@ -4700,9 +4793,9 @@ export type EventSubscribeOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly reasoningID: string
|
||||
readonly ordinal: number
|
||||
readonly text: string
|
||||
readonly providerMetadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
readonly state?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -4757,12 +4850,9 @@ export type EventSubscribeOutput =
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly tool: string
|
||||
readonly input: { readonly [x: string]: unknown }
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
readonly executed: boolean
|
||||
readonly state?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -4801,10 +4891,8 @@ export type EventSubscribeOutput =
|
||||
>
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly result?: unknown
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
readonly executed: boolean
|
||||
readonly resultState?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -4818,34 +4906,36 @@ export type EventSubscribeOutput =
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly callID: string
|
||||
readonly error: { readonly type: "unknown"; readonly message: string }
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
readonly result?: unknown
|
||||
readonly provider: {
|
||||
readonly executed: boolean
|
||||
readonly metadata?: { readonly [x: string]: { readonly [x: string]: unknown } }
|
||||
}
|
||||
readonly executed: boolean
|
||||
readonly resultState?: { readonly [x: string]: unknown }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.retried"
|
||||
readonly type: "session.retry.scheduled"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: {
|
||||
readonly sessionID: string
|
||||
readonly assistantMessageID: string
|
||||
readonly attempt: number
|
||||
readonly error: {
|
||||
readonly message: string
|
||||
readonly statusCode?: number
|
||||
readonly isRetryable: boolean
|
||||
readonly responseHeaders?: { readonly [x: string]: string }
|
||||
readonly responseBody?: string
|
||||
readonly metadata?: { readonly [x: string]: string }
|
||||
}
|
||||
readonly at: number
|
||||
readonly error: { readonly type: string; readonly message: string }
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.compaction.admitted"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly inputID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
@@ -4877,6 +4967,15 @@ export type EventSubscribeOutput =
|
||||
readonly recent: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
readonly metadata?: { readonly [x: string]: unknown }
|
||||
readonly type: "session.compaction.failed"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
readonly created: number
|
||||
@@ -4917,7 +5016,7 @@ export type EventSubscribeOutput =
|
||||
readonly type: "session.revert.committed"
|
||||
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
|
||||
readonly location?: { readonly directory: string; readonly workspaceID?: string }
|
||||
readonly data: { readonly sessionID: string; readonly messageID: string }
|
||||
readonly data: { readonly sessionID: string; readonly to: string }
|
||||
}
|
||||
| {
|
||||
readonly id: string
|
||||
|
||||
@@ -89,6 +89,9 @@ test("session methods retain decoded Effect inputs and outputs", async () => {
|
||||
if (url.includes("/prompt")) {
|
||||
return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(admission)))
|
||||
}
|
||||
if (url.endsWith("/compact")) {
|
||||
return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json(compactionAdmission)))
|
||||
}
|
||||
if (url.includes("/context")) {
|
||||
return Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ data: [] })))
|
||||
}
|
||||
@@ -218,6 +221,16 @@ const admission = {
|
||||
},
|
||||
}
|
||||
|
||||
const compactionAdmission = {
|
||||
data: {
|
||||
type: "compaction",
|
||||
admittedSeq: 1,
|
||||
id: "msg_compaction",
|
||||
sessionID: "ses_test",
|
||||
timeCreated: 1_717_171_717_000,
|
||||
},
|
||||
}
|
||||
|
||||
const modelSwitchedMessage = {
|
||||
id: "msg_model",
|
||||
type: "model-switched",
|
||||
|
||||
@@ -193,6 +193,7 @@ test("session methods use the public HTTP contract", async () => {
|
||||
})
|
||||
}
|
||||
if (url.includes("/prompt")) return Response.json(admission)
|
||||
if (url.endsWith("/compact")) return Response.json(compactionAdmission)
|
||||
if (url.includes("/context")) return Response.json({ data: [] })
|
||||
if (url.includes("/message/")) return Response.json({ data: modelSwitchedMessage })
|
||||
if (url.endsWith("/api/session/active"))
|
||||
@@ -317,6 +318,16 @@ const admission = {
|
||||
},
|
||||
}
|
||||
|
||||
const compactionAdmission = {
|
||||
data: {
|
||||
type: "compaction",
|
||||
admittedSeq: 1,
|
||||
id: "msg_compaction",
|
||||
sessionID: "ses_test",
|
||||
timeCreated: 1_717_171_717_000,
|
||||
},
|
||||
}
|
||||
|
||||
const modelSwitchedMessage = {
|
||||
id: "msg_model",
|
||||
type: "model-switched",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"id": "96e9fe64-d810-4102-8f79-3317a88bb6d2",
|
||||
"id": "afa00750-fad8-473a-b877-13ff35ea3411",
|
||||
"prevIds": [
|
||||
"22e57fed-b9b8-4e94-a3b4-f94bece680a8"
|
||||
"96e9fe64-d810-4102-8f79-3317a88bb6d2"
|
||||
],
|
||||
"ddl": [
|
||||
{
|
||||
@@ -1012,13 +1012,23 @@
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "type",
|
||||
"entityType": "columns",
|
||||
"table": "session_input"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
"name": "prompt",
|
||||
"entityType": "columns",
|
||||
"table": "session_input"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"notNull": true,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": null,
|
||||
"generated": null,
|
||||
@@ -2066,6 +2076,10 @@
|
||||
"value": "promoted_seq",
|
||||
"isExpression": false
|
||||
},
|
||||
{
|
||||
"value": "type",
|
||||
"isExpression": false
|
||||
},
|
||||
{
|
||||
"value": "delivery",
|
||||
"isExpression": false
|
||||
@@ -2078,7 +2092,21 @@
|
||||
"isUnique": false,
|
||||
"where": null,
|
||||
"origin": "manual",
|
||||
"name": "session_input_session_pending_delivery_seq_idx",
|
||||
"name": "session_input_session_pending_type_delivery_seq_idx",
|
||||
"entityType": "indexes",
|
||||
"table": "session_input"
|
||||
},
|
||||
{
|
||||
"columns": [
|
||||
{
|
||||
"value": "session_id",
|
||||
"isExpression": false
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"session_input\".\"type\" = 'compaction' and \"session_input\".\"promoted_seq\" is null",
|
||||
"origin": "manual",
|
||||
"name": "session_input_session_pending_compaction_idx",
|
||||
"entityType": "indexes",
|
||||
"table": "session_input"
|
||||
},
|
||||
|
||||
@@ -2,8 +2,7 @@ export * as ConfigExternalPlugin from "./external"
|
||||
|
||||
import type { Plugin as EffectPlugin } from "@opencode-ai/plugin/v2/effect"
|
||||
import type { Plugin as PromisePlugin } from "@opencode-ai/plugin/v2/promise"
|
||||
import { Effect, Schema, Stream } from "effect"
|
||||
import { createRequire } from "node:module"
|
||||
import { Effect, Schema } from "effect"
|
||||
import path from "path"
|
||||
import { fileURLToPath, pathToFileURL } from "url"
|
||||
import { Config } from "../../config"
|
||||
@@ -36,9 +35,6 @@ const PluginPackage = Schema.Struct({
|
||||
module: Schema.optional(Schema.String),
|
||||
})
|
||||
|
||||
let importGeneration = 0
|
||||
const moduleCache = createRequire(import.meta.url).cache
|
||||
|
||||
export const Plugin = define({
|
||||
id: "config-plugin",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
@@ -46,7 +42,6 @@ export const Plugin = define({
|
||||
const fs = yield* FSUtil.Service
|
||||
const location = yield* Location.Service
|
||||
const npm = yield* Npm.Service
|
||||
const active = new Set<string>()
|
||||
const load = Effect.fn("ConfigExternalPlugin.load")(function* () {
|
||||
const configured: { package: string; options?: Record<string, unknown> }[] = []
|
||||
|
||||
@@ -110,7 +105,7 @@ export const Plugin = define({
|
||||
: (yield* npm.add(ref.package)).entrypoint
|
||||
if (!entrypoint) return
|
||||
yield* Effect.log({ msg: "loading plugin", id: ref.package, entrypoint })
|
||||
const mod = yield* Effect.promise(() => import(cacheBust(entrypoint)))
|
||||
const mod = yield* Effect.promise(() => import(entrypoint))
|
||||
const value = (yield* Schema.decodeUnknownEffect(PluginModule)(mod)).default
|
||||
const plugin = "effect" in value ? value : PluginPromise.fromPromise(value)
|
||||
return {
|
||||
@@ -118,40 +113,13 @@ export const Plugin = define({
|
||||
effect: (host: Parameters<typeof plugin.effect>[0]) =>
|
||||
plugin.effect({ ...host, options: ref.options ?? {} }),
|
||||
}
|
||||
}).pipe(
|
||||
Effect.catchCause((cause) =>
|
||||
Effect.logError("failed to load plugin", { package: ref.package, cause }).pipe(Effect.as(undefined)),
|
||||
),
|
||||
),
|
||||
}).pipe(Effect.catchCause(() => Effect.succeed(undefined))),
|
||||
).pipe(Effect.map((plugins) => plugins.filter((plugin) => plugin !== undefined)))
|
||||
})
|
||||
const reconcile = Effect.fn("ConfigExternalPlugin.reconcile")(function* () {
|
||||
const plugins = yield* load()
|
||||
const next = new Set(plugins.map((plugin) => plugin.id))
|
||||
for (const id of active) {
|
||||
if (!next.has(id)) yield* ctx.plugin.remove(id)
|
||||
}
|
||||
for (const plugin of plugins) yield* ctx.plugin.add(plugin)
|
||||
active.clear()
|
||||
for (const id of next) active.add(id)
|
||||
})
|
||||
|
||||
yield* reconcile()
|
||||
yield* ctx.event.subscribe().pipe(
|
||||
Stream.filter((event) => event.type === "config.updated"),
|
||||
Stream.runForEach(() => reconcile()),
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
)
|
||||
for (const plugin of yield* load()) yield* ctx.plugin.add(plugin)
|
||||
}),
|
||||
})
|
||||
|
||||
function cacheBust(entrypoint: string) {
|
||||
const url = path.isAbsolute(entrypoint) ? pathToFileURL(entrypoint) : new URL(entrypoint)
|
||||
if (url.protocol === "file:") delete moduleCache[fileURLToPath(url)]
|
||||
url.searchParams.set("opencode-reload", String(++importGeneration))
|
||||
return url.href
|
||||
}
|
||||
|
||||
const resolvePackageEntrypoint = Effect.fnUntraced(function* (fs: FSUtil.Interface, directory: string) {
|
||||
const pkg = yield* fs.readJson(path.join(directory, "package.json")).pipe(
|
||||
Effect.flatMap(Schema.decodeUnknownEffect(PluginPackage)),
|
||||
|
||||
+2
@@ -44,5 +44,7 @@ export const migrations = (
|
||||
import("./migration/20260703090000_reset_v2_event_rename_sweep"),
|
||||
import("./migration/20260703181610_event_created_column"),
|
||||
import("./migration/20260703190000_reset_v2_shell_event_payloads"),
|
||||
import("./migration/20260703200000_reset_v2_session_events"),
|
||||
import("./migration/20260704161518_durable_session_inbox"),
|
||||
])
|
||||
).map((module) => module.default) satisfies DatabaseMigration.Migration[]
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260703200000_reset_v2_session_events",
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`DELETE FROM \`session_input\`;`)
|
||||
yield* tx.run(`DELETE FROM \`session_message\`;`)
|
||||
yield* tx.run(`DELETE FROM \`event\`;`)
|
||||
yield* tx.run(`DELETE FROM \`event_sequence\`;`)
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Effect } from "effect"
|
||||
import type { DatabaseMigration } from "../migration"
|
||||
|
||||
export default {
|
||||
id: "20260704161518_durable_session_inbox",
|
||||
up(tx) {
|
||||
return Effect.gen(function* () {
|
||||
yield* tx.run(`PRAGMA foreign_keys=OFF;`)
|
||||
yield* tx.run(`
|
||||
CREATE TABLE \`__new_session_input\` (
|
||||
\`id\` text PRIMARY KEY,
|
||||
\`session_id\` text NOT NULL,
|
||||
\`type\` text NOT NULL,
|
||||
\`prompt\` text,
|
||||
\`delivery\` text,
|
||||
\`admitted_seq\` integer NOT NULL,
|
||||
\`promoted_seq\` integer,
|
||||
\`time_created\` integer NOT NULL,
|
||||
CONSTRAINT \`fk_session_input_session_id_session_id_fk\` FOREIGN KEY (\`session_id\`) REFERENCES \`session\`(\`id\`) ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
yield* tx.run(
|
||||
`INSERT INTO \`__new_session_input\`(\`id\`, \`session_id\`, \`type\`, \`prompt\`, \`delivery\`, \`admitted_seq\`, \`promoted_seq\`, \`time_created\`) SELECT \`id\`, \`session_id\`, 'prompt', \`prompt\`, \`delivery\`, \`admitted_seq\`, \`promoted_seq\`, \`time_created\` FROM \`session_input\`;`,
|
||||
)
|
||||
yield* tx.run(`DROP TABLE \`session_input\`;`)
|
||||
yield* tx.run(`ALTER TABLE \`__new_session_input\` RENAME TO \`session_input\`;`)
|
||||
yield* tx.run(`PRAGMA foreign_keys=ON;`)
|
||||
yield* tx.run(`DROP INDEX IF EXISTS \`session_input_session_pending_delivery_seq_idx\`;`)
|
||||
yield* tx.run(
|
||||
`CREATE INDEX \`session_input_session_pending_type_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`type\`,\`delivery\`,\`admitted_seq\`);`,
|
||||
)
|
||||
yield* tx.run(
|
||||
`CREATE UNIQUE INDEX \`session_input_session_pending_compaction_idx\` ON \`session_input\` (\`session_id\`) WHERE "session_input"."type" = 'compaction' and "session_input"."promoted_seq" is null;`,
|
||||
)
|
||||
yield* tx.run(
|
||||
`CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`,
|
||||
)
|
||||
yield* tx.run(
|
||||
`CREATE UNIQUE INDEX \`session_input_session_promoted_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`);`,
|
||||
)
|
||||
})
|
||||
},
|
||||
} satisfies DatabaseMigration.Migration
|
||||
@@ -170,8 +170,9 @@ export default {
|
||||
CREATE TABLE \`session_input\` (
|
||||
\`id\` text PRIMARY KEY,
|
||||
\`session_id\` text NOT NULL,
|
||||
\`prompt\` text NOT NULL,
|
||||
\`delivery\` text NOT NULL,
|
||||
\`type\` text NOT NULL,
|
||||
\`prompt\` text,
|
||||
\`delivery\` text,
|
||||
\`admitted_seq\` integer NOT NULL,
|
||||
\`promoted_seq\` integer,
|
||||
\`time_created\` integer NOT NULL,
|
||||
@@ -259,7 +260,10 @@ export default {
|
||||
yield* tx.run(`CREATE INDEX \`part_message_id_id_idx\` ON \`part\` (\`message_id\`,\`id\`);`)
|
||||
yield* tx.run(`CREATE INDEX \`part_session_idx\` ON \`part\` (\`session_id\`);`)
|
||||
yield* tx.run(
|
||||
`CREATE INDEX \`session_input_session_pending_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`delivery\`,\`admitted_seq\`);`,
|
||||
`CREATE INDEX \`session_input_session_pending_type_delivery_seq_idx\` ON \`session_input\` (\`session_id\`,\`promoted_seq\`,\`type\`,\`delivery\`,\`admitted_seq\`);`,
|
||||
)
|
||||
yield* tx.run(
|
||||
`CREATE UNIQUE INDEX \`session_input_session_pending_compaction_idx\` ON \`session_input\` (\`session_id\`) WHERE "session_input"."type" = 'compaction' and "session_input"."promoted_seq" is null;`,
|
||||
)
|
||||
yield* tx.run(
|
||||
`CREATE UNIQUE INDEX \`session_input_session_admitted_seq_idx\` ON \`session_input\` (\`session_id\`,\`admitted_seq\`);`,
|
||||
|
||||
@@ -45,7 +45,7 @@ const FILES = [
|
||||
"**/.nyc_output/**",
|
||||
]
|
||||
|
||||
export const PATTERNS = [...FILES, ...FOLDERS]
|
||||
export const PATTERNS = [...FILES, ...FOLDERS, `**/{${Array.from(FOLDERS).join(",")}}/**`]
|
||||
|
||||
export function match(filepath: string, opts?: { extra?: string[]; whitelist?: string[] }) {
|
||||
for (const pattern of opts?.whitelist || []) {
|
||||
|
||||
@@ -59,7 +59,14 @@ export type AskResult = typeof AskResult.Type
|
||||
|
||||
export const Event = Permission.Event
|
||||
|
||||
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionV2.RejectedError", {}) {}
|
||||
export class RejectedError extends Schema.TaggedErrorClass<RejectedError>()("PermissionV2.RejectedError", {
|
||||
permission: Schema.String,
|
||||
resources: Schema.Array(Schema.String),
|
||||
}) {
|
||||
override get message() {
|
||||
return `Permission rejected: ${this.permission}`
|
||||
}
|
||||
}
|
||||
|
||||
export class CorrectedError extends Schema.TaggedErrorClass<CorrectedError>()("PermissionV2.CorrectedError", {
|
||||
feedback: Schema.String,
|
||||
@@ -67,7 +74,13 @@ export class CorrectedError extends Schema.TaggedErrorClass<CorrectedError>()("P
|
||||
|
||||
export class DeniedError extends Schema.TaggedErrorClass<DeniedError>()("PermissionV2.DeniedError", {
|
||||
rules: Permission.Ruleset,
|
||||
}) {}
|
||||
permission: Schema.String,
|
||||
resources: Schema.Array(Schema.String),
|
||||
}) {
|
||||
override get message() {
|
||||
return `Permission denied: ${this.permission}`
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("PermissionV2.NotFoundError", {
|
||||
requestID: ID,
|
||||
@@ -117,9 +130,11 @@ const layer = Layer.effect(
|
||||
const sessions = yield* SessionStore.Service
|
||||
const saved = yield* PermissionSaved.Service
|
||||
const pending = new Map<ID, Pending>()
|
||||
const rejected = (request: Request) =>
|
||||
new RejectedError({ permission: request.action, resources: [...request.resources] })
|
||||
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.forEach(pending.values(), (item) => Deferred.fail(item.deferred, new RejectedError()), {
|
||||
Effect.forEach(pending.values(), (item) => Deferred.fail(item.deferred, rejected(item.request)), {
|
||||
discard: true,
|
||||
}).pipe(
|
||||
Effect.ensuring(
|
||||
@@ -201,6 +216,8 @@ const layer = Layer.effect(
|
||||
if (result.effect === "deny") {
|
||||
return yield* new DeniedError({
|
||||
rules: relevant(input, result.rules),
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
})
|
||||
}
|
||||
if (result.effect === "allow") return
|
||||
@@ -230,7 +247,7 @@ const layer = Layer.effect(
|
||||
if (input.reply === "reject") {
|
||||
yield* Deferred.fail(
|
||||
existing.deferred,
|
||||
input.message ? new CorrectedError({ feedback: input.message }) : new RejectedError(),
|
||||
input.message ? new CorrectedError({ feedback: input.message }) : rejected(existing.request),
|
||||
)
|
||||
pending.delete(input.requestID)
|
||||
for (const [id, item] of pending) {
|
||||
@@ -240,7 +257,7 @@ const layer = Layer.effect(
|
||||
requestID: item.request.id,
|
||||
reply: "reject",
|
||||
})
|
||||
yield* Deferred.fail(item.deferred, new RejectedError())
|
||||
yield* Deferred.fail(item.deferred, rejected(item.request))
|
||||
pending.delete(id)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -33,7 +33,6 @@ import { MessageDecodeError } from "./session/error"
|
||||
import { SessionEvent } from "./session/event"
|
||||
import { SessionInput } from "./session/input"
|
||||
import { Snapshot } from "./snapshot"
|
||||
import { SessionCompaction } from "./session/compaction"
|
||||
import { SessionRevert } from "./session/revert"
|
||||
import { Revert } from "@opencode-ai/schema/revert"
|
||||
import { FSUtil } from "./fs-util"
|
||||
@@ -94,6 +93,7 @@ type CreateInput = CreateBaseInput &
|
||||
({ location: Location.Ref; parentID?: never } | { parentID: SessionSchema.ID; location?: never })
|
||||
|
||||
type CompactInput = {
|
||||
id?: SessionMessage.ID
|
||||
sessionID: SessionSchema.ID
|
||||
}
|
||||
|
||||
@@ -119,6 +119,13 @@ export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictE
|
||||
sessionID: SessionSchema.ID,
|
||||
messageID: SessionMessage.ID,
|
||||
}) {}
|
||||
export class CompactionConflictError extends Schema.TaggedErrorClass<CompactionConflictError>()(
|
||||
"Session.CompactionConflictError",
|
||||
{
|
||||
sessionID: SessionSchema.ID,
|
||||
inputID: SessionMessage.ID,
|
||||
},
|
||||
) {}
|
||||
export class BusyError extends Schema.TaggedErrorClass<BusyError>()("Session.BusyError", {
|
||||
sessionID: SessionSchema.ID,
|
||||
}) {}
|
||||
@@ -133,6 +140,7 @@ export type Error =
|
||||
| MessageDecodeError
|
||||
| OperationUnavailableError
|
||||
| PromptConflictError
|
||||
| CompactionConflictError
|
||||
| BusyError
|
||||
| SkillNotFoundError
|
||||
| CommandV2.NotFoundError
|
||||
@@ -220,7 +228,7 @@ export interface Interface {
|
||||
}) => Effect.Effect<void, NotFoundError | SkillNotFoundError>
|
||||
readonly compact: (
|
||||
input: CompactInput,
|
||||
) => Effect.Effect<void, NotFoundError | BusyError | MessageDecodeError | OperationUnavailableError>
|
||||
) => Effect.Effect<SessionInput.Compaction, NotFoundError | CompactionConflictError>
|
||||
readonly wait: (id: SessionSchema.ID) => Effect.Effect<void, NotFoundError>
|
||||
readonly active: Effect.Effect<ReadonlySet<SessionSchema.ID>>
|
||||
readonly background: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError>
|
||||
@@ -540,7 +548,8 @@ const layer = Layer.effect(
|
||||
const started = yield* Effect.gen(function* () {
|
||||
const shell = yield* Shell.Service
|
||||
return yield* shell.create({ command: input.command, cwd: session.location.directory })
|
||||
}).pipe(Effect.provide(locations.get(session.location)))
|
||||
})
|
||||
.pipe(Effect.provide(locations.get(session.location)))
|
||||
yield* events.publish(
|
||||
SessionEvent.Shell.Started,
|
||||
{
|
||||
@@ -563,8 +572,7 @@ const layer = Layer.effect(
|
||||
.pipe(Effect.catchTag("Shell.NotFoundError", () => Effect.succeed(missingShellOutput())))
|
||||
: missingShellOutput()
|
||||
return { shell: terminal.info, output }
|
||||
})
|
||||
.pipe(Effect.provide(locations.get(session.location)))
|
||||
}).pipe(Effect.provide(locations.get(session.location)))
|
||||
yield* events.publish(SessionEvent.Shell.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
shell: completed.shell,
|
||||
@@ -623,19 +631,20 @@ const layer = Layer.effect(
|
||||
})
|
||||
}),
|
||||
compact: Effect.fn("V2Session.compact")(function* (input) {
|
||||
const session = yield* result.get(input.sessionID)
|
||||
// TODO: admit manual compaction as durable pending work, like prompt input, instead of rejecting active sessions.
|
||||
if ((yield* execution.active).has(input.sessionID)) return yield* new BusyError({ sessionID: input.sessionID })
|
||||
const context = yield* store.context(input.sessionID)
|
||||
const compacted = yield* Effect.gen(function* () {
|
||||
const compaction = yield* SessionCompaction.Service
|
||||
return yield* compaction.compactManual({ session, messages: context })
|
||||
yield* result.get(input.sessionID)
|
||||
const inputID = input.id ?? SessionMessage.ID.create()
|
||||
const admitted = yield* SessionInput.admitCompaction(db, events, {
|
||||
id: inputID,
|
||||
sessionID: input.sessionID,
|
||||
}).pipe(
|
||||
Effect.provide(locations.get(session.location)),
|
||||
Effect.catch(() => Effect.succeed(false)),
|
||||
Effect.catchDefect((defect) =>
|
||||
defect instanceof SessionInput.LifecycleConflict
|
||||
? new CompactionConflictError({ sessionID: input.sessionID, inputID })
|
||||
: Effect.die(defect),
|
||||
),
|
||||
)
|
||||
if (!compacted) return yield* new OperationUnavailableError({ operation: "compact" })
|
||||
return undefined
|
||||
yield* execution.wake(input.sessionID)
|
||||
return admitted
|
||||
}),
|
||||
wait: Effect.fn("V2Session.wait")(function* (sessionID) {
|
||||
yield* result.get(sessionID)
|
||||
|
||||
@@ -18,32 +18,32 @@ const TOOL_OUTPUT_MAX_CHARS = 2_000
|
||||
const SUMMARY_OUTPUT_TOKENS = 4_096
|
||||
const SUMMARY_TEMPLATE = `Output exactly the Markdown structure shown inside <template> and keep the section order unchanged. Do not include the <template> tags in your response.
|
||||
<template>
|
||||
## Goal
|
||||
## Continuation Goal
|
||||
- [single-sentence task summary]
|
||||
|
||||
## Constraints & Preferences
|
||||
## Operating Constraints
|
||||
- [user constraints, preferences, specs, or "(none)"]
|
||||
|
||||
## Progress
|
||||
### Done
|
||||
### Completed
|
||||
- [completed work or "(none)"]
|
||||
|
||||
### In Progress
|
||||
### In Flight
|
||||
- [current work or "(none)"]
|
||||
|
||||
### Blocked
|
||||
- [blockers or "(none)"]
|
||||
|
||||
## Key Decisions
|
||||
## Decisions To Preserve
|
||||
- [decision and why, or "(none)"]
|
||||
|
||||
## Next Steps
|
||||
## Resume From Here
|
||||
- [ordered next actions or "(none)"]
|
||||
|
||||
## Critical Context
|
||||
## Context To Preserve
|
||||
- [important technical facts, errors, open questions, or "(none)"]
|
||||
|
||||
## Relevant Files
|
||||
## Working Files
|
||||
- [file or directory path: why it matters, or "(none)"]
|
||||
</template>
|
||||
|
||||
@@ -225,7 +225,13 @@ const make = (dependencies: Dependencies) => {
|
||||
.pipe(
|
||||
Stream.runForEach((event) => {
|
||||
if (LLMEvent.is.providerError(event)) failed = true
|
||||
if (LLMEvent.is.textDelta(event)) chunks.push(event.text)
|
||||
if (LLMEvent.is.textDelta(event)) {
|
||||
chunks.push(event.text)
|
||||
return dependencies.events.publish(SessionEvent.Compaction.Delta, {
|
||||
sessionID: input.sessionID,
|
||||
text: event.text,
|
||||
})
|
||||
}
|
||||
return Effect.void
|
||||
}),
|
||||
Effect.as(true),
|
||||
@@ -262,7 +268,9 @@ const make = (dependencies: Dependencies) => {
|
||||
if (context === undefined || context <= 0) return false
|
||||
const selected = select(input.messages, config.tokens)
|
||||
if (!selected) return false
|
||||
const previousSummary = input.messages.find((message) => message.type === "compaction")
|
||||
const previousSummary = input.messages.find(
|
||||
(message) => message.type === "compaction" && message.status === "completed",
|
||||
)
|
||||
const hasHead = selected.head.length > 0
|
||||
if (!hasHead && previousSummary?.type !== "compaction" && !input.force) return false
|
||||
const forcedShortContext = input.force && !hasHead
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Schema } from "effect"
|
||||
import { SessionMessage } from "./message"
|
||||
import { SessionSchema } from "./schema"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
|
||||
export class MessageDecodeError extends Schema.TaggedErrorClass<MessageDecodeError>()("Session.MessageDecodeError", {
|
||||
sessionID: SessionSchema.ID,
|
||||
@@ -10,3 +11,20 @@ export class MessageDecodeError extends Schema.TaggedErrorClass<MessageDecodeErr
|
||||
return `Failed to decode message ${this.messageID} in session ${this.sessionID}`
|
||||
}
|
||||
}
|
||||
|
||||
export class StepFailedError extends Schema.TaggedErrorClass<StepFailedError>()("Session.StepFailedError", {
|
||||
error: SessionError.Error,
|
||||
}) {
|
||||
override get message() {
|
||||
return this.error.message
|
||||
}
|
||||
}
|
||||
|
||||
export class UserInterruptedError extends Schema.TaggedErrorClass<UserInterruptedError>()(
|
||||
"Session.UserInterruptedError",
|
||||
{},
|
||||
) {
|
||||
override get message() {
|
||||
return "Session interrupted by user"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Cause, DateTime, Effect, Exit, Layer } from "effect"
|
||||
import { Cause, Effect, Exit, Layer } from "effect"
|
||||
import { EventV2 } from "../../event"
|
||||
import { LocationServiceMap } from "../../location-service-map"
|
||||
import { makeGlobalNode } from "../../effect/app-node"
|
||||
@@ -8,6 +8,16 @@ import { SessionRunner } from "../runner"
|
||||
import { SessionSchema } from "../schema"
|
||||
import { SessionStore } from "../store"
|
||||
import { SessionExecution } from "../execution"
|
||||
import { toSessionError } from "../to-session-error"
|
||||
import { UserInterruptedError } from "../error"
|
||||
|
||||
export function terminal(exit: Exit.Exit<void, SessionRunner.RunError>, reason?: "user" | "shutdown" | "superseded") {
|
||||
if (Exit.isSuccess(exit)) return { type: "succeeded" as const }
|
||||
if (Cause.hasInterrupts(exit.cause)) return { type: "interrupted" as const, reason: reason ?? "shutdown" }
|
||||
const failure = Cause.squash(exit.cause)
|
||||
if (failure instanceof UserInterruptedError) return { type: "interrupted" as const, reason: "user" as const }
|
||||
return { type: "failed" as const, error: toSessionError(failure) }
|
||||
}
|
||||
|
||||
/** Current-process routing for implicit-local Locations. Future remote placement belongs here. */
|
||||
const layer = Layer.effect(
|
||||
@@ -16,7 +26,23 @@ const layer = Layer.effect(
|
||||
const store = yield* SessionStore.Service
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const events = yield* EventV2.Service
|
||||
const coordinator = yield* SessionRunCoordinator.make<SessionSchema.ID, SessionRunner.RunError>({
|
||||
const reportLifecycle = <A>(sessionID: SessionSchema.ID, effect: Effect.Effect<A>) =>
|
||||
effect.pipe(
|
||||
Effect.tapCause((cause) =>
|
||||
Cause.hasInterruptsOnly(cause)
|
||||
? Effect.void
|
||||
: Effect.logError("Failed to publish Session execution lifecycle", cause).pipe(
|
||||
Effect.annotateLogs({ sessionID }),
|
||||
),
|
||||
),
|
||||
Effect.asVoid,
|
||||
)
|
||||
const coordinator = yield* SessionRunCoordinator.make<
|
||||
SessionSchema.ID,
|
||||
SessionRunner.RunError,
|
||||
"user" | "shutdown" | "superseded"
|
||||
>({
|
||||
started: (sessionID) => reportLifecycle(sessionID, events.publish(SessionEvent.Execution.Started, { sessionID })),
|
||||
drain: Effect.fnUntraced(function* (sessionID: SessionSchema.ID, force) {
|
||||
const session = yield* store.get(sessionID)
|
||||
if (!session) return yield* Effect.die(new Error(`Session not found: ${sessionID}`))
|
||||
@@ -29,28 +55,31 @@ const layer = Layer.effect(
|
||||
),
|
||||
)
|
||||
}),
|
||||
// One ExecutionSettled per execution (busy period), covering every coalesced drain.
|
||||
settled: (sessionID, exit) =>
|
||||
Effect.gen(function* () {
|
||||
const failure =
|
||||
Exit.isFailure(exit) && !Cause.hasInterrupts(exit.cause) ? Cause.squash(exit.cause) : undefined
|
||||
yield* events.publish(SessionEvent.ExecutionSettled, {
|
||||
sessionID,
|
||||
outcome: Exit.isSuccess(exit) ? "success" : Cause.hasInterrupts(exit.cause) ? "interrupted" : "failure",
|
||||
error:
|
||||
failure !== undefined
|
||||
? { type: "unknown", message: failure instanceof Error ? failure.message : String(failure) }
|
||||
: undefined,
|
||||
})
|
||||
}).pipe(
|
||||
Effect.catchCause(() => Effect.void),
|
||||
Effect.asVoid,
|
||||
// One terminal observation per busy period, covering every coalesced drain.
|
||||
settled: (sessionID, exit, reason) =>
|
||||
reportLifecycle(
|
||||
sessionID,
|
||||
Effect.gen(function* () {
|
||||
const outcome = terminal(exit, reason)
|
||||
if (outcome.type === "succeeded") {
|
||||
yield* events.publish(SessionEvent.Execution.Succeeded, { sessionID })
|
||||
return
|
||||
}
|
||||
if (outcome.type === "interrupted") {
|
||||
yield* events.publish(SessionEvent.Execution.Interrupted, { sessionID, reason: outcome.reason })
|
||||
return
|
||||
}
|
||||
yield* events.publish(SessionEvent.Execution.Failed, {
|
||||
sessionID,
|
||||
error: outcome.error,
|
||||
})
|
||||
}),
|
||||
),
|
||||
})
|
||||
|
||||
return SessionExecution.Service.of({
|
||||
active: coordinator.active,
|
||||
interrupt: coordinator.interrupt,
|
||||
interrupt: (sessionID) => coordinator.interrupt(sessionID, "user"),
|
||||
resume: coordinator.run,
|
||||
wake: coordinator.wake,
|
||||
awaitIdle: coordinator.awaitIdle,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { and, asc, desc, eq, gt, gte, ne, or } from "drizzle-orm"
|
||||
import { and, asc, desc, eq, gt, gte, ne, or, sql } from "drizzle-orm"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { Database } from "../database/database"
|
||||
import { MessageDecodeError } from "./error"
|
||||
@@ -14,7 +14,13 @@ export const latestCompaction = Effect.fnUntraced(function* (db: DatabaseService
|
||||
return yield* db
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(and(eq(SessionMessageTable.session_id, sessionID), eq(SessionMessageTable.type, "compaction")))
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.session_id, sessionID),
|
||||
eq(SessionMessageTable.type, "compaction"),
|
||||
sql`json_extract(${SessionMessageTable.data}, '$.status') = 'completed'`,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(SessionMessageTable.seq))
|
||||
.limit(1)
|
||||
.get()
|
||||
|
||||
@@ -2,9 +2,10 @@ export * as SessionInput from "./input"
|
||||
|
||||
import { and, asc, eq, isNull } from "drizzle-orm"
|
||||
import { DateTime, Effect, Schema } from "effect"
|
||||
import { Admitted, Delivery } from "@opencode-ai/schema/session-input"
|
||||
import { Admitted, Compaction, Delivery, Entry, PromptEntry } from "@opencode-ai/schema/session-input"
|
||||
import type { Database } from "../database/database"
|
||||
import type { EventV2 } from "../event"
|
||||
import { KeyedMutex } from "../effect/keyed-mutex"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionMessage } from "./message"
|
||||
import { Prompt } from "./prompt"
|
||||
@@ -13,30 +14,77 @@ import { SessionInputTable, SessionMessageTable } from "./sql"
|
||||
|
||||
type DatabaseService = Database.Interface["db"]
|
||||
|
||||
export { Admitted, Delivery }
|
||||
export { Admitted, Compaction, Delivery, Entry, PromptEntry }
|
||||
|
||||
const decodePrompt = Schema.decodeUnknownSync(Prompt)
|
||||
const encodePrompt = Schema.encodeSync(Prompt)
|
||||
const inboxLocks = KeyedMutex.makeUnsafe<SessionSchema.ID>()
|
||||
|
||||
const fromRow = (row: typeof SessionInputTable.$inferSelect): Admitted =>
|
||||
Admitted.make({
|
||||
export class LifecycleConflict extends Schema.TaggedErrorClass<LifecycleConflict>()("SessionInput.LifecycleConflict", {
|
||||
id: SessionMessage.ID,
|
||||
}) {}
|
||||
|
||||
const fromRow = (row: typeof SessionInputTable.$inferSelect): Entry => {
|
||||
const base = {
|
||||
admittedSeq: row.admitted_seq,
|
||||
id: SessionMessage.ID.make(row.id),
|
||||
sessionID: SessionSchema.ID.make(row.session_id),
|
||||
timeCreated: DateTime.makeUnsafe(row.time_created),
|
||||
}
|
||||
if (row.type === "compaction")
|
||||
return Compaction.make({
|
||||
...base,
|
||||
type: "compaction",
|
||||
...(row.promoted_seq === null ? {} : { handledSeq: row.promoted_seq }),
|
||||
})
|
||||
if (!row.prompt || !row.delivery) throw new LifecycleConflict({ id: base.id })
|
||||
return PromptEntry.make({
|
||||
...base,
|
||||
type: "prompt",
|
||||
prompt: decodePrompt(row.prompt),
|
||||
delivery: row.delivery,
|
||||
timeCreated: DateTime.makeUnsafe(row.time_created),
|
||||
...(row.promoted_seq === null ? {} : { promotedSeq: row.promoted_seq }),
|
||||
})
|
||||
}
|
||||
|
||||
const toAdmitted = (entry: PromptEntry): Admitted =>
|
||||
Admitted.make({
|
||||
admittedSeq: entry.admittedSeq,
|
||||
id: entry.id,
|
||||
sessionID: entry.sessionID,
|
||||
prompt: entry.prompt,
|
||||
delivery: entry.delivery,
|
||||
timeCreated: entry.timeCreated,
|
||||
...(entry.promotedSeq === undefined ? {} : { promotedSeq: entry.promotedSeq }),
|
||||
})
|
||||
|
||||
export const find = Effect.fn("SessionInput.find")(function* (db: DatabaseService, id: SessionMessage.ID) {
|
||||
const row = yield* db.select().from(SessionInputTable).where(eq(SessionInputTable.id, id)).get().pipe(Effect.orDie)
|
||||
return row === undefined ? undefined : fromRow(row)
|
||||
})
|
||||
|
||||
export class LifecycleConflict extends Schema.TaggedErrorClass<LifecycleConflict>()("SessionInput.LifecycleConflict", {
|
||||
id: SessionMessage.ID,
|
||||
}) {}
|
||||
export const pendingCompaction = Effect.fn("SessionInput.pendingCompaction")(function* (
|
||||
db: DatabaseService,
|
||||
sessionID: SessionSchema.ID,
|
||||
) {
|
||||
const row = yield* db
|
||||
.select()
|
||||
.from(SessionInputTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionInputTable.session_id, sessionID),
|
||||
eq(SessionInputTable.type, "compaction"),
|
||||
isNull(SessionInputTable.promoted_seq),
|
||||
),
|
||||
)
|
||||
.orderBy(asc(SessionInputTable.admitted_seq))
|
||||
.limit(1)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row) return
|
||||
const entry = fromRow(row)
|
||||
return entry.type === "compaction" ? entry : undefined
|
||||
})
|
||||
|
||||
export const admit = Effect.fn("SessionInput.admit")(function* (
|
||||
db: DatabaseService,
|
||||
@@ -49,7 +97,10 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
|
||||
},
|
||||
) {
|
||||
const existing = yield* find(db, input.id)
|
||||
if (existing !== undefined) return existing
|
||||
if (existing !== undefined) {
|
||||
if (existing.type !== "prompt") return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
return toAdmitted(existing)
|
||||
}
|
||||
return yield* events
|
||||
.publish(SessionEvent.PromptAdmitted, {
|
||||
inputID: input.id,
|
||||
@@ -73,11 +124,54 @@ export const admit = Effect.fn("SessionInput.admit")(function* (
|
||||
),
|
||||
),
|
||||
Effect.catchDefect((defect) =>
|
||||
find(db, input.id).pipe(Effect.flatMap((stored) => (stored ? Effect.succeed(stored) : Effect.die(defect)))),
|
||||
find(db, input.id).pipe(
|
||||
Effect.flatMap((stored) =>
|
||||
stored?.type === "prompt" ? Effect.succeed(toAdmitted(stored)) : Effect.die(defect),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
export const admitCompaction = Effect.fn("SessionInput.admitCompaction")(function* (
|
||||
db: DatabaseService,
|
||||
events: EventV2.Interface,
|
||||
input: { readonly id: SessionMessage.ID; readonly sessionID: SessionSchema.ID },
|
||||
) {
|
||||
return yield* inboxLocks.withLock(input.sessionID)(
|
||||
Effect.gen(function* () {
|
||||
const exact = yield* find(db, input.id)
|
||||
if (exact) {
|
||||
if (exact.type === "compaction" && exact.sessionID === input.sessionID) return exact
|
||||
return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
}
|
||||
const pending = yield* pendingCompaction(db, input.sessionID)
|
||||
if (pending) return pending
|
||||
return yield* events
|
||||
.publish(SessionEvent.Compaction.Admitted, {
|
||||
inputID: input.id,
|
||||
sessionID: input.sessionID,
|
||||
})
|
||||
.pipe(
|
||||
Effect.flatMap((event) => {
|
||||
if (event.durable === undefined)
|
||||
return Effect.die(new Error("Compaction admission event is missing aggregate sequence"))
|
||||
return pendingCompaction(db, input.sessionID).pipe(
|
||||
Effect.flatMap((stored) =>
|
||||
stored ? Effect.succeed(stored) : Effect.die(new LifecycleConflict({ id: input.id })),
|
||||
),
|
||||
)
|
||||
}),
|
||||
Effect.catchDefect((defect) =>
|
||||
pendingCompaction(db, input.sessionID).pipe(
|
||||
Effect.flatMap((stored) => (stored ? Effect.succeed(stored) : Effect.die(defect))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
export const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(function* (
|
||||
db: DatabaseService,
|
||||
input: {
|
||||
@@ -101,6 +195,7 @@ export const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(functio
|
||||
.values({
|
||||
id: input.id,
|
||||
session_id: input.sessionID,
|
||||
type: "prompt",
|
||||
admitted_seq: input.admittedSeq,
|
||||
prompt: encodePrompt(input.prompt),
|
||||
delivery: input.delivery,
|
||||
@@ -113,6 +208,44 @@ export const projectAdmitted = Effect.fn("SessionInput.projectAdmitted")(functio
|
||||
if (!stored) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
})
|
||||
|
||||
export const projectCompactionAdmitted = Effect.fn("SessionInput.projectCompactionAdmitted")(function* (
|
||||
db: DatabaseService,
|
||||
input: {
|
||||
readonly admittedSeq: number
|
||||
readonly id: SessionMessage.ID
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly timeCreated: DateTime.Utc
|
||||
},
|
||||
) {
|
||||
const message = yield* db
|
||||
.select({ id: SessionMessageTable.id })
|
||||
.from(SessionMessageTable)
|
||||
.where(eq(SessionMessageTable.id, input.id))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (message !== undefined) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
const stored = yield* db
|
||||
.insert(SessionInputTable)
|
||||
.values({
|
||||
id: input.id,
|
||||
session_id: input.sessionID,
|
||||
type: "compaction",
|
||||
admitted_seq: input.admittedSeq,
|
||||
time_created: DateTime.toEpochMillis(input.timeCreated),
|
||||
})
|
||||
.onConflictDoNothing()
|
||||
.returning()
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (stored) {
|
||||
const entry = fromRow(stored)
|
||||
return entry.type === "compaction" ? entry : yield* Effect.die(new LifecycleConflict({ id: entry.id }))
|
||||
}
|
||||
const pending = yield* pendingCompaction(db, input.sessionID)
|
||||
if (pending) return pending
|
||||
return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
})
|
||||
|
||||
export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromoted")(function* (
|
||||
db: DatabaseService,
|
||||
input: {
|
||||
@@ -121,6 +254,7 @@ export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromot
|
||||
readonly promotedSeq: number
|
||||
},
|
||||
) {
|
||||
if (yield* pendingCompaction(db, input.sessionID)) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
const updated = yield* db
|
||||
.update(SessionInputTable)
|
||||
.set({ promoted_seq: input.promotedSeq })
|
||||
@@ -128,6 +262,7 @@ export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromot
|
||||
and(
|
||||
eq(SessionInputTable.id, input.id),
|
||||
eq(SessionInputTable.session_id, input.sessionID),
|
||||
eq(SessionInputTable.type, "prompt"),
|
||||
isNull(SessionInputTable.promoted_seq),
|
||||
),
|
||||
)
|
||||
@@ -136,29 +271,58 @@ export const projectPromptPromoted = Effect.fn("SessionInput.projectPromptPromot
|
||||
.pipe(Effect.orDie)
|
||||
if (updated) {
|
||||
const stored = fromRow(updated)
|
||||
if (stored.sessionID !== input.sessionID) return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
if (stored.type !== "prompt" || stored.sessionID !== input.sessionID)
|
||||
return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
return stored
|
||||
}
|
||||
|
||||
// Every PromptPromoted event is published from an admitted inbox row, so a missing or
|
||||
// divergent row on replay is an invariant violation.
|
||||
const stored = yield* find(db, input.id)
|
||||
if (!stored || stored.sessionID !== input.sessionID || stored.promotedSeq !== input.promotedSeq)
|
||||
if (
|
||||
!stored ||
|
||||
stored.type !== "prompt" ||
|
||||
stored.sessionID !== input.sessionID ||
|
||||
stored.promotedSeq !== input.promotedSeq
|
||||
)
|
||||
return yield* Effect.die(new LifecycleConflict({ id: input.id }))
|
||||
return stored
|
||||
})
|
||||
|
||||
export const settleCompaction = Effect.fn("SessionInput.settleCompaction")(function* (
|
||||
db: DatabaseService,
|
||||
input: { readonly sessionID: SessionSchema.ID; readonly handledSeq: number },
|
||||
) {
|
||||
const updated = yield* db
|
||||
.update(SessionInputTable)
|
||||
.set({ promoted_seq: input.handledSeq })
|
||||
.where(
|
||||
and(
|
||||
eq(SessionInputTable.session_id, input.sessionID),
|
||||
eq(SessionInputTable.type, "compaction"),
|
||||
isNull(SessionInputTable.promoted_seq),
|
||||
),
|
||||
)
|
||||
.returning()
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (updated) {
|
||||
const stored = fromRow(updated)
|
||||
return stored.type === "compaction" ? stored : yield* Effect.die(new LifecycleConflict({ id: stored.id }))
|
||||
}
|
||||
return undefined
|
||||
})
|
||||
|
||||
export const hasPending = Effect.fn("SessionInput.hasPending")(function* (
|
||||
db: DatabaseService,
|
||||
sessionID: SessionSchema.ID,
|
||||
delivery: Delivery,
|
||||
) {
|
||||
if (yield* pendingCompaction(db, sessionID)) return false
|
||||
const row = yield* db
|
||||
.select({ id: SessionInputTable.id })
|
||||
.from(SessionInputTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionInputTable.session_id, sessionID),
|
||||
eq(SessionInputTable.type, "prompt"),
|
||||
isNull(SessionInputTable.promoted_seq),
|
||||
eq(SessionInputTable.delivery, delivery),
|
||||
),
|
||||
@@ -181,42 +345,44 @@ export const equivalent = (
|
||||
input.sessionID === expected.sessionID &&
|
||||
JSON.stringify(encodePrompt(input.prompt)) === JSON.stringify(encodePrompt(expected.prompt))
|
||||
|
||||
const matchesProjection = (
|
||||
input: Admitted,
|
||||
expected: {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly prompt: Prompt
|
||||
readonly delivery: Delivery
|
||||
readonly timeCreated: DateTime.Utc
|
||||
},
|
||||
) =>
|
||||
equivalent(input, expected) &&
|
||||
DateTime.toEpochMillis(input.timeCreated) === DateTime.toEpochMillis(expected.timeCreated)
|
||||
|
||||
const publish = Effect.fn("SessionInput.publish")(function* (
|
||||
db: DatabaseService,
|
||||
events: EventV2.Interface,
|
||||
sessionID: SessionSchema.ID,
|
||||
rows: ReadonlyArray<typeof SessionInputTable.$inferSelect>,
|
||||
) {
|
||||
for (const row of rows) {
|
||||
const id = SessionMessage.ID.make(row.id)
|
||||
yield* events
|
||||
.publish(SessionEvent.PromptPromoted, {
|
||||
sessionID,
|
||||
inputID: id,
|
||||
})
|
||||
.pipe(
|
||||
Effect.catchDefect((defect) =>
|
||||
defect instanceof LifecycleConflict
|
||||
? find(db, id).pipe(
|
||||
Effect.flatMap((stored) => (stored?.promotedSeq === undefined ? Effect.die(defect) : Effect.void)),
|
||||
)
|
||||
: Effect.die(defect),
|
||||
),
|
||||
return yield* inboxLocks.withLock(sessionID)(
|
||||
Effect.gen(function* () {
|
||||
if (yield* pendingCompaction(db, sessionID)) return 0
|
||||
yield* Effect.forEach(
|
||||
rows,
|
||||
(row) => {
|
||||
const entry = fromRow(row)
|
||||
if (entry.type !== "prompt") return Effect.die(new LifecycleConflict({ id: entry.id }))
|
||||
return events
|
||||
.publish(SessionEvent.PromptPromoted, {
|
||||
sessionID,
|
||||
inputID: entry.id,
|
||||
})
|
||||
.pipe(
|
||||
Effect.catchDefect((defect) =>
|
||||
defect instanceof LifecycleConflict
|
||||
? find(db, entry.id).pipe(
|
||||
Effect.flatMap((stored) =>
|
||||
stored?.type === "prompt" && stored.promotedSeq !== undefined
|
||||
? Effect.void
|
||||
: Effect.die(defect),
|
||||
),
|
||||
)
|
||||
: Effect.die(defect),
|
||||
),
|
||||
)
|
||||
},
|
||||
{ discard: true },
|
||||
)
|
||||
}
|
||||
return rows.length
|
||||
return rows.length
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
|
||||
@@ -224,12 +390,14 @@ export const promoteSteers = Effect.fn("SessionInput.promoteSteers")(function* (
|
||||
events: EventV2.Interface,
|
||||
sessionID: SessionSchema.ID,
|
||||
) {
|
||||
if (yield* pendingCompaction(db, sessionID)) return 0
|
||||
const rows = yield* db
|
||||
.select()
|
||||
.from(SessionInputTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionInputTable.session_id, sessionID),
|
||||
eq(SessionInputTable.type, "prompt"),
|
||||
isNull(SessionInputTable.promoted_seq),
|
||||
eq(SessionInputTable.delivery, "steer"),
|
||||
),
|
||||
@@ -245,12 +413,14 @@ export const promoteNextQueued = Effect.fn("SessionInput.promoteNextQueued")(fun
|
||||
events: EventV2.Interface,
|
||||
sessionID: SessionSchema.ID,
|
||||
) {
|
||||
if (yield* pendingCompaction(db, sessionID)) return false
|
||||
const row = yield* db
|
||||
.select()
|
||||
.from(SessionInputTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionInputTable.session_id, sessionID),
|
||||
eq(SessionInputTable.type, "prompt"),
|
||||
isNull(SessionInputTable.promoted_seq),
|
||||
eq(SessionInputTable.delivery, "queue"),
|
||||
),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { castDraft, produce, type WritableDraft } from "immer"
|
||||
import { Effect } from "effect"
|
||||
import { DateTime, Effect } from "effect"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionMessage } from "./message"
|
||||
|
||||
@@ -8,6 +8,7 @@ export type MemoryState = {
|
||||
}
|
||||
|
||||
export interface Adapter {
|
||||
readonly getModel: () => Effect.Effect<SessionMessage.ModelSelected["model"] | undefined, never, never>
|
||||
readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined, never, never>
|
||||
readonly getAssistant: (
|
||||
messageID: SessionMessage.ID,
|
||||
@@ -15,8 +16,10 @@ export interface Adapter {
|
||||
readonly getShell: (
|
||||
shellID: SessionMessage.Shell["shell"]["id"],
|
||||
) => Effect.Effect<SessionMessage.Shell | undefined, never, never>
|
||||
readonly getCompaction: () => Effect.Effect<SessionMessage.Compaction | undefined, never, never>
|
||||
readonly updateAssistant: (assistant: SessionMessage.Assistant) => Effect.Effect<void, never, never>
|
||||
readonly updateShell: (shell: SessionMessage.Shell) => Effect.Effect<void, never, never>
|
||||
readonly updateCompaction: (compaction: SessionMessage.Compaction) => Effect.Effect<void, never, never>
|
||||
readonly appendMessage: (message: SessionMessage.Message) => Effect.Effect<void, never, never>
|
||||
}
|
||||
|
||||
@@ -25,10 +28,23 @@ export function memory(state: MemoryState): Adapter {
|
||||
state.messages.findLastIndex((message) => message.id === messageID)
|
||||
const shellIndex = (messageID: SessionMessage.ID) =>
|
||||
state.messages.findLastIndex((message) => message.id === messageID)
|
||||
const compactionIndex = () =>
|
||||
state.messages.findLastIndex(
|
||||
(message) => message.type === "compaction" && (message.status === "queued" || message.status === "running"),
|
||||
)
|
||||
// A newer step supersedes stale incomplete rows; never resume an older assistant projection.
|
||||
const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant")
|
||||
|
||||
return {
|
||||
getModel() {
|
||||
return Effect.sync(
|
||||
() =>
|
||||
state.messages.findLast(
|
||||
(message): message is SessionMessage.ModelSelected | SessionMessage.Assistant =>
|
||||
message.type === "model-switched" || message.type === "assistant",
|
||||
)?.model,
|
||||
)
|
||||
},
|
||||
getCurrentAssistant() {
|
||||
return Effect.sync(() => {
|
||||
const index = latestAssistantIndex()
|
||||
@@ -52,6 +68,13 @@ export function memory(state: MemoryState): Adapter {
|
||||
})
|
||||
})
|
||||
},
|
||||
getCompaction() {
|
||||
return Effect.sync(() => {
|
||||
const index = compactionIndex()
|
||||
const message = state.messages[index]
|
||||
return message?.type === "compaction" ? message : undefined
|
||||
})
|
||||
},
|
||||
updateAssistant(assistant) {
|
||||
return Effect.sync(() => {
|
||||
const index = assistantIndex(assistant.id)
|
||||
@@ -70,6 +93,12 @@ export function memory(state: MemoryState): Adapter {
|
||||
state.messages[index] = shell
|
||||
})
|
||||
},
|
||||
updateCompaction(compaction) {
|
||||
return Effect.sync(() => {
|
||||
const index = state.messages.findLastIndex((message) => message.id === compaction.id)
|
||||
if (index >= 0) state.messages[index] = compaction
|
||||
})
|
||||
},
|
||||
appendMessage(message) {
|
||||
return Effect.sync(() => {
|
||||
state.messages.push(message)
|
||||
@@ -89,11 +118,11 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
(item): item is DraftTool => item.type === "tool" && (callID === undefined || item.id === callID),
|
||||
)
|
||||
|
||||
const latestText = (assistant: DraftAssistant | undefined, textID: string) =>
|
||||
assistant?.content.findLast((item): item is DraftText => item.type === "text" && item.id === textID)
|
||||
const latestText = (assistant: DraftAssistant | undefined) =>
|
||||
assistant?.content.findLast((item): item is DraftText => item.type === "text")
|
||||
|
||||
const latestReasoning = (assistant: DraftAssistant | undefined, reasoningID: string) =>
|
||||
assistant?.content.findLast((item): item is DraftReasoning => item.type === "reasoning" && item.id === reasoningID)
|
||||
const latestReasoning = (assistant: DraftAssistant | undefined) =>
|
||||
assistant?.content.findLast((item): item is DraftReasoning => item.type === "reasoning" && !item.time?.completed)
|
||||
|
||||
const updateOwnedAssistant = (messageID: SessionMessage.ID, recipe: (draft: DraftAssistant) => void) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -101,6 +130,17 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
if (assistant) yield* adapter.updateAssistant(produce(assistant, recipe))
|
||||
})
|
||||
|
||||
const clearCurrentRetry = Effect.gen(function* () {
|
||||
const assistant = yield* adapter.getCurrentAssistant()
|
||||
if (assistant?.retry) {
|
||||
yield* adapter.updateAssistant(
|
||||
produce(assistant, (draft) => {
|
||||
draft.retry = undefined
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return Effect.gen(function* () {
|
||||
yield* SessionEvent.All.match(event, {
|
||||
"session.agent.selected": (event) => {
|
||||
@@ -115,22 +155,29 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
)
|
||||
},
|
||||
"session.model.selected": (event) => {
|
||||
return adapter.appendMessage(
|
||||
SessionMessage.ModelSelected.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "model-switched",
|
||||
metadata: event.metadata,
|
||||
model: event.data.model,
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
return Effect.gen(function* () {
|
||||
const previous = yield* adapter.getModel()
|
||||
yield* adapter.appendMessage(
|
||||
SessionMessage.ModelSelected.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "model-switched",
|
||||
metadata: event.metadata,
|
||||
model: event.data.model,
|
||||
previous,
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
"session.moved": () => Effect.void,
|
||||
"session.renamed": () => Effect.void,
|
||||
"session.forked": () => Effect.void,
|
||||
"session.prompt.promoted": () => Effect.void,
|
||||
"session.prompt.admitted": () => Effect.void,
|
||||
"session.execution.settled": () => Effect.void,
|
||||
"session.execution.started": () => Effect.void,
|
||||
"session.execution.succeeded": () => clearCurrentRetry,
|
||||
"session.execution.failed": () => clearCurrentRetry,
|
||||
"session.execution.interrupted": () => clearCurrentRetry,
|
||||
"session.context.updated": (event) =>
|
||||
adapter.appendMessage(
|
||||
SessionMessage.System.make({
|
||||
@@ -191,10 +238,26 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
},
|
||||
"session.step.started": (event) => {
|
||||
return Effect.gen(function* () {
|
||||
const existing = yield* adapter.getAssistant(event.data.assistantMessageID)
|
||||
if (existing) {
|
||||
yield* adapter.updateAssistant(
|
||||
produce(existing, (draft) => {
|
||||
draft.agent = event.data.agent
|
||||
draft.model = castDraft(event.data.model)
|
||||
draft.retry = undefined
|
||||
draft.error = undefined
|
||||
draft.finish = undefined
|
||||
draft.time.completed = undefined
|
||||
if (event.data.snapshot) draft.snapshot = { ...draft.snapshot, start: event.data.snapshot }
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
const currentAssistant = yield* adapter.getCurrentAssistant()
|
||||
if (currentAssistant) {
|
||||
yield* adapter.updateAssistant(
|
||||
produce(currentAssistant, (draft) => {
|
||||
draft.retry = undefined
|
||||
draft.time.completed = event.created
|
||||
}),
|
||||
)
|
||||
@@ -230,25 +293,24 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.time.completed = event.created
|
||||
draft.finish = "error"
|
||||
draft.error = event.data.error
|
||||
draft.error = castDraft(event.data.error)
|
||||
draft.retry = undefined
|
||||
})
|
||||
},
|
||||
"session.text.started": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.content.push(
|
||||
castDraft(SessionMessage.AssistantText.make({ type: "text", id: event.data.textID, text: "" })),
|
||||
)
|
||||
draft.content.push(castDraft(SessionMessage.AssistantText.make({ type: "text", text: "" })))
|
||||
})
|
||||
},
|
||||
"session.text.delta": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestText(draft, event.data.textID)
|
||||
const match = latestText(draft)
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
},
|
||||
"session.text.ended": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestText(draft, event.data.textID)
|
||||
const match = latestText(draft)
|
||||
if (match) match.text = event.data.text
|
||||
})
|
||||
},
|
||||
@@ -278,7 +340,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
if (match) {
|
||||
match.provider = event.data.provider
|
||||
match.executed = event.data.executed
|
||||
match.providerState = event.data.state
|
||||
match.time.ran = event.created
|
||||
match.state = castDraft(
|
||||
SessionMessage.ToolStateRunning.make({
|
||||
@@ -304,11 +367,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
if (match && match.state.status === "running") {
|
||||
match.provider = {
|
||||
executed: event.data.provider.executed || match.provider?.executed === true,
|
||||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.data.provider.metadata,
|
||||
}
|
||||
match.executed = event.data.executed || match.executed === true
|
||||
match.providerResultState = event.data.resultState
|
||||
match.time.completed = event.created
|
||||
match.state = castDraft(
|
||||
SessionMessage.ToolStateCompleted.make({
|
||||
@@ -327,11 +387,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestTool(draft, event.data.callID)
|
||||
if (match && (match.state.status === "pending" || match.state.status === "running")) {
|
||||
match.provider = {
|
||||
executed: event.data.provider.executed || match.provider?.executed === true,
|
||||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.data.provider.metadata,
|
||||
}
|
||||
match.executed = event.data.executed || match.executed === true
|
||||
match.providerResultState = event.data.resultState
|
||||
match.time.completed = event.created
|
||||
match.state = castDraft(
|
||||
SessionMessage.ToolStateError.make({
|
||||
@@ -352,9 +409,8 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
castDraft(
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
id: event.data.reasoningID,
|
||||
text: "",
|
||||
providerMetadata: event.data.providerMetadata,
|
||||
state: event.data.state,
|
||||
time: { created: event.created },
|
||||
}),
|
||||
),
|
||||
@@ -363,36 +419,83 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||
},
|
||||
"session.reasoning.delta": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestReasoning(draft, event.data.reasoningID)
|
||||
const match = latestReasoning(draft)
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
},
|
||||
"session.reasoning.ended": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
const match = latestReasoning(draft, event.data.reasoningID)
|
||||
const match = latestReasoning(draft)
|
||||
if (match) {
|
||||
match.text = event.data.text
|
||||
match.time = { created: match.time?.created ?? event.created, completed: event.created }
|
||||
if (event.data.providerMetadata !== undefined) match.providerMetadata = event.data.providerMetadata
|
||||
if (event.data.state !== undefined) match.state = event.data.state
|
||||
}
|
||||
})
|
||||
},
|
||||
"session.retried": () => Effect.void,
|
||||
"session.compaction.started": () => Effect.void,
|
||||
"session.compaction.delta": () => Effect.void,
|
||||
"session.compaction.ended": (event) => {
|
||||
return adapter.appendMessage(
|
||||
"session.retry.scheduled": (event) => {
|
||||
return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
|
||||
draft.retry = {
|
||||
attempt: event.data.attempt,
|
||||
at: DateTime.makeUnsafe(event.data.at),
|
||||
error: castDraft(event.data.error),
|
||||
}
|
||||
})
|
||||
},
|
||||
"session.compaction.admitted": (event) =>
|
||||
adapter.appendMessage(
|
||||
SessionMessage.Compaction.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
id: event.data.inputID,
|
||||
type: "compaction",
|
||||
status: "queued",
|
||||
metadata: event.metadata,
|
||||
reason: event.data.reason,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
reason: "manual",
|
||||
summary: "",
|
||||
recent: "",
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
),
|
||||
"session.compaction.started": (event) =>
|
||||
Effect.gen(function* () {
|
||||
if (event.data.reason !== "manual") return
|
||||
const current = yield* adapter.getCompaction()
|
||||
if (!current) return
|
||||
yield* adapter.updateCompaction({ ...current, status: "running" })
|
||||
}),
|
||||
"session.compaction.delta": () => Effect.void,
|
||||
"session.compaction.ended": (event) => {
|
||||
return Effect.gen(function* () {
|
||||
const current = event.data.reason === "manual" ? yield* adapter.getCompaction() : undefined
|
||||
if (current) {
|
||||
yield* adapter.updateCompaction({
|
||||
...current,
|
||||
status: "completed",
|
||||
reason: event.data.reason,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
})
|
||||
return
|
||||
}
|
||||
yield* adapter.appendMessage(
|
||||
SessionMessage.Compaction.make({
|
||||
id: SessionMessage.ID.fromEvent(event.id),
|
||||
type: "compaction",
|
||||
status: "completed",
|
||||
metadata: event.metadata,
|
||||
reason: event.data.reason,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
time: { created: event.created },
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
"session.compaction.failed": () =>
|
||||
Effect.gen(function* () {
|
||||
const current = yield* adapter.getCompaction()
|
||||
if (!current) return
|
||||
yield* adapter.updateCompaction({ ...current, status: "failed" })
|
||||
}),
|
||||
"session.revert.staged": () => Effect.void,
|
||||
"session.revert.cleared": () => Effect.void,
|
||||
"session.revert.committed": () => Effect.void,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DateTime, Effect, Layer, Schema } from "effect"
|
||||
import { Database } from "../database/database"
|
||||
import { EventV2 } from "../event"
|
||||
import { makeGlobalNode } from "../effect/app-node"
|
||||
import { ModelV2 } from "../model"
|
||||
import { SessionEvent } from "./event"
|
||||
import { SessionV1 } from "../v1/session"
|
||||
import { WorkspaceTable } from "../control-plane/workspace.sql"
|
||||
@@ -242,6 +243,7 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
||||
eq(SessionMessageTable.session_id, event.data.parentID),
|
||||
gt(SessionMessageTable.seq, cursor),
|
||||
copiedSeq === 0 ? undefined : lt(SessionMessageTable.seq, copiedSeq + 1),
|
||||
sql`${SessionMessageTable.type} != 'compaction' or json_extract(${SessionMessageTable.data}, '$.status') not in ('queued', 'running')`,
|
||||
),
|
||||
)
|
||||
.orderBy(asc(SessionMessageTable.seq))
|
||||
@@ -291,11 +293,12 @@ const projectFork = Effect.fn("SessionProjector.projectFork")(function* (
|
||||
.values(
|
||||
inputRows.flatMap((row) => {
|
||||
const id = idMap.get(row.id)
|
||||
return id
|
||||
return id && row.type === "prompt"
|
||||
? [
|
||||
{
|
||||
id,
|
||||
session_id: event.data.sessionID,
|
||||
type: "prompt" as const,
|
||||
prompt: row.prompt,
|
||||
delivery: row.delivery,
|
||||
admitted_seq: row.admitted_seq,
|
||||
@@ -356,6 +359,17 @@ function run(db: DatabaseService, event: MessageEvent) {
|
||||
}
|
||||
const appendMessage = (message: SessionMessage.Message) => insertMessage(db, event, message)
|
||||
const adapter: SessionMessageUpdater.Adapter = {
|
||||
getModel() {
|
||||
return db
|
||||
.select({ model: SessionTable.model })
|
||||
.from(SessionTable)
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.get()
|
||||
.pipe(
|
||||
Effect.orDie,
|
||||
Effect.map((row) => (row?.model ? Schema.decodeUnknownSync(ModelV2.Ref)(row.model) : undefined)),
|
||||
)
|
||||
},
|
||||
getCurrentAssistant() {
|
||||
return Effect.gen(function* () {
|
||||
// A newer step supersedes stale incomplete rows; never resume an older assistant projection.
|
||||
@@ -414,8 +428,30 @@ function run(db: DatabaseService, event: MessageEvent) {
|
||||
return message.type === "shell" ? message : undefined
|
||||
})
|
||||
},
|
||||
getCompaction() {
|
||||
return Effect.gen(function* () {
|
||||
const row = yield* db
|
||||
.select()
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.session_id, event.data.sessionID),
|
||||
eq(SessionMessageTable.type, "compaction"),
|
||||
sql`json_extract(${SessionMessageTable.data}, '$.status') in ('queued', 'running')`,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(SessionMessageTable.seq))
|
||||
.limit(1)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!row) return
|
||||
const message = decodeRow(row)
|
||||
return message.type === "compaction" ? message : undefined
|
||||
})
|
||||
},
|
||||
updateAssistant: updateMessage,
|
||||
updateShell: updateMessage,
|
||||
updateCompaction: updateMessage,
|
||||
appendMessage,
|
||||
}
|
||||
yield* SessionMessageUpdater.update(adapter, event)
|
||||
@@ -570,13 +606,13 @@ const layer = Layer.effectDiscard(
|
||||
)
|
||||
yield* events.project(SessionEvent.ModelSelected, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* run(db, event)
|
||||
yield* db
|
||||
.update(SessionTable)
|
||||
.set({ model: event.data.model, time_updated: DateTime.toEpochMillis(event.created) })
|
||||
.where(eq(SessionTable.id, event.data.sessionID))
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* run(db, event)
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.Renamed, (event) =>
|
||||
@@ -622,6 +658,23 @@ const layer = Layer.effectDiscard(
|
||||
})
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.Compaction.Admitted, (event) =>
|
||||
Effect.gen(function* () {
|
||||
if (event.durable === undefined)
|
||||
return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
const admitted = yield* SessionInput.projectCompactionAdmitted(db, {
|
||||
admittedSeq: event.durable.seq,
|
||||
id: event.data.inputID,
|
||||
sessionID: event.data.sessionID,
|
||||
timeCreated: event.created,
|
||||
})
|
||||
if (admitted.id !== event.data.inputID) return
|
||||
yield* run(db, event)
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.Execution.Succeeded, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Execution.Failed, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Execution.Interrupted, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.ContextUpdated, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Synthetic, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Skill.Activated, (event) =>
|
||||
@@ -648,8 +701,31 @@ const layer = Layer.effectDiscard(
|
||||
yield* events.project(SessionEvent.Tool.Failed, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Reasoning.Started, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Reasoning.Ended, (event) => run(db, event))
|
||||
// yield* events.project(SessionEvent.Retried, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Compaction.Ended, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.RetryScheduled, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Compaction.Started, (event) => run(db, event))
|
||||
yield* events.project(SessionEvent.Compaction.Ended, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* run(db, event)
|
||||
if (event.durable === undefined)
|
||||
return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
if (event.data.reason === "manual")
|
||||
yield* SessionInput.settleCompaction(db, {
|
||||
sessionID: event.data.sessionID,
|
||||
handledSeq: event.durable.seq,
|
||||
})
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.Compaction.Failed, (event) =>
|
||||
Effect.gen(function* () {
|
||||
yield* run(db, event)
|
||||
if (event.durable === undefined)
|
||||
return yield* Effect.die(new Error("Durable Session event is missing aggregate sequence"))
|
||||
yield* SessionInput.settleCompaction(db, {
|
||||
sessionID: event.data.sessionID,
|
||||
handledSeq: event.durable.seq,
|
||||
})
|
||||
}),
|
||||
)
|
||||
yield* events.project(SessionEvent.RevertEvent.Staged, (event) =>
|
||||
db
|
||||
.update(SessionTable)
|
||||
@@ -675,14 +751,11 @@ const layer = Layer.effectDiscard(
|
||||
.select({ seq: SessionMessageTable.seq })
|
||||
.from(SessionMessageTable)
|
||||
.where(
|
||||
and(
|
||||
eq(SessionMessageTable.session_id, event.data.sessionID),
|
||||
eq(SessionMessageTable.id, event.data.messageID),
|
||||
),
|
||||
and(eq(SessionMessageTable.session_id, event.data.sessionID), eq(SessionMessageTable.id, event.data.to)),
|
||||
)
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
if (!boundary) return yield* Effect.die(new Error(`Revert boundary message not found: ${event.data.messageID}`))
|
||||
if (!boundary) return yield* Effect.die(new Error(`Revert boundary message not found: ${event.data.to}`))
|
||||
yield* db
|
||||
.delete(SessionMessageTable)
|
||||
.where(
|
||||
|
||||
@@ -113,6 +113,6 @@ export const commit = Effect.fn("SessionRevert.commit")(function* (session: Sess
|
||||
const events = yield* EventV2.Service
|
||||
yield* events.publish(SessionEvent.RevertEvent.Committed, {
|
||||
sessionID: session.id,
|
||||
messageID: session.revert.messageID,
|
||||
to: session.revert.messageID,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ export * as SessionRunCoordinator from "./run-coordinator"
|
||||
import { Deferred, Effect, Exit, Fiber, FiberSet, Scope } from "effect"
|
||||
|
||||
/** Serializes execution for each key while allowing different keys to run concurrently. */
|
||||
export interface Coordinator<Key, E> {
|
||||
export interface Coordinator<Key, E, Reason = never> {
|
||||
/** Snapshots keys with an execution owned by this coordinator. */
|
||||
readonly active: Effect.Effect<ReadonlySet<Key>>
|
||||
/** Starts an execution while idle, or joins the active execution and returns its exit. */
|
||||
@@ -11,7 +11,7 @@ export interface Coordinator<Key, E> {
|
||||
/** Rings the doorbell: an idle key starts an execution; an active one drains again before settling. */
|
||||
readonly wake: (key: Key) => Effect.Effect<void>
|
||||
/** Stops the active execution, clears its doorbell, and waits for cleanup. No-op when idle. */
|
||||
readonly interrupt: (key: Key) => Effect.Effect<void>
|
||||
readonly interrupt: (key: Key, reason?: Reason) => Effect.Effect<void>
|
||||
/** Resolves once no execution is active for the key. Returns immediately when already idle and never starts work. */
|
||||
readonly awaitIdle: (key: Key) => Effect.Effect<void>
|
||||
}
|
||||
@@ -23,11 +23,13 @@ export interface Coordinator<Key, E> {
|
||||
* closes the gap between a drain's last eligibility check and the idle transition, since
|
||||
* those cannot be one atomic step. `done` resolves joiners with this execution's exit.
|
||||
*/
|
||||
type Execution<E> = {
|
||||
type Execution<E, Reason> = {
|
||||
readonly done: Deferred.Deferred<void, E>
|
||||
owner?: Fiber.Fiber<void>
|
||||
pendingWake: boolean
|
||||
stopping: boolean
|
||||
settling: boolean
|
||||
interruptionReason?: Reason
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,19 +43,21 @@ type Execution<E> = {
|
||||
* waiters get this exit
|
||||
* ```
|
||||
*/
|
||||
export const make = <Key, E>(options: {
|
||||
export const make = <Key, E, Reason = never>(options: {
|
||||
readonly drain: (key: Key, force: boolean) => Effect.Effect<void, E>
|
||||
/** Runs once when a process-local busy period begins, before its first drain. */
|
||||
readonly started?: (key: Key) => Effect.Effect<void>
|
||||
/**
|
||||
* Runs in the execution fiber for every exit, including interruption, after the final
|
||||
* drain and before the execution settles (waiters resolve after it completes).
|
||||
*/
|
||||
readonly settled?: (key: Key, exit: Exit.Exit<void, E>) => Effect.Effect<void>
|
||||
}): Effect.Effect<Coordinator<Key, E>, never, Scope.Scope> =>
|
||||
readonly settled?: (key: Key, exit: Exit.Exit<void, E>, reason?: Reason) => Effect.Effect<void>
|
||||
}): Effect.Effect<Coordinator<Key, E, Reason>, never, Scope.Scope> =>
|
||||
Effect.gen(function* () {
|
||||
const executions = new Map<Key, Execution<E>>()
|
||||
const executions = new Map<Key, Execution<E, Reason>>()
|
||||
const fork = yield* FiberSet.makeRuntime<never, void, never>()
|
||||
|
||||
const loop = (key: Key, execution: Execution<E>, force: boolean): Effect.Effect<void, E> =>
|
||||
const loop = (key: Key, execution: Execution<E, Reason>, force: boolean): Effect.Effect<void, E> =>
|
||||
Effect.suspend(() => options.drain(key, force)).pipe(
|
||||
Effect.flatMap(() =>
|
||||
Effect.suspend(() => {
|
||||
@@ -66,15 +70,25 @@ export const make = <Key, E>(options: {
|
||||
)
|
||||
|
||||
const start = (key: Key, force: boolean) => {
|
||||
const execution: Execution<E> = { done: Deferred.makeUnsafe<void, E>(), pendingWake: false, stopping: false }
|
||||
const execution: Execution<E, Reason> = {
|
||||
done: Deferred.makeUnsafe<void, E>(),
|
||||
pendingWake: false,
|
||||
stopping: false,
|
||||
settling: false,
|
||||
}
|
||||
executions.set(key, execution)
|
||||
// The leading yield lets `owner` be assigned before the drain can settle, and keeps
|
||||
// failing self-waking executions from growing the stack across successor starts.
|
||||
// Drains start one tick after wake; callers observe progress through events or run.
|
||||
execution.owner = fork(
|
||||
Effect.yieldNow.pipe(
|
||||
Effect.andThen(Effect.uninterruptible(options.started?.(key) ?? Effect.void)),
|
||||
Effect.andThen(loop(key, execution, force)),
|
||||
Effect.onExit((exit) => options.settled?.(key, exit) ?? Effect.void),
|
||||
Effect.onExit((exit) =>
|
||||
Effect.sync(() => {
|
||||
execution.settling = true
|
||||
}).pipe(Effect.andThen(options.settled?.(key, exit, execution.interruptionReason) ?? Effect.void)),
|
||||
),
|
||||
Effect.onExit((exit) => Effect.sync(() => settle(key, execution, exit))),
|
||||
Effect.exit,
|
||||
Effect.asVoid,
|
||||
@@ -85,7 +99,7 @@ export const make = <Key, E>(options: {
|
||||
|
||||
// A doorbell that survives the execution loop (rung after the loop decided to end, or
|
||||
// during failure or interruption cleanup) starts a fresh execution for the remaining work.
|
||||
const settle = (key: Key, execution: Execution<E>, exit: Exit.Exit<void, E>) => {
|
||||
const settle = (key: Key, execution: Execution<E, Reason>, exit: Exit.Exit<void, E>) => {
|
||||
if (execution.pendingWake) start(key, false)
|
||||
else executions.delete(key)
|
||||
Deferred.doneUnsafe(execution.done, exit)
|
||||
@@ -112,12 +126,13 @@ export const make = <Key, E>(options: {
|
||||
start(key, false)
|
||||
})
|
||||
|
||||
const interrupt = (key: Key): Effect.Effect<void> =>
|
||||
const interrupt = (key: Key, reason?: Reason): Effect.Effect<void> =>
|
||||
Effect.suspend(() => {
|
||||
const execution = executions.get(key)
|
||||
if (execution?.owner === undefined) return Effect.void
|
||||
if (execution?.owner === undefined || execution.stopping || execution.settling) return Effect.void
|
||||
execution.stopping = true
|
||||
execution.pendingWake = false
|
||||
execution.interruptionReason = reason
|
||||
return Fiber.interrupt(execution.owner)
|
||||
})
|
||||
|
||||
|
||||
@@ -3,13 +3,19 @@ export * as SessionRunner from "./index"
|
||||
import type { LLMError } from "@opencode-ai/llm"
|
||||
import { Context, Effect } from "effect"
|
||||
import { SessionSchema } from "../schema"
|
||||
import type { MessageDecodeError } from "../error"
|
||||
import type { MessageDecodeError, StepFailedError, UserInterruptedError } from "../error"
|
||||
import { SessionRunnerModel } from "./model"
|
||||
import type { SystemContext } from "../../system-context/index"
|
||||
import type { ToolOutputStore } from "../../tool-output-store"
|
||||
|
||||
export type RunError =
|
||||
LLMError | SessionRunnerModel.Error | MessageDecodeError | SystemContext.InitializationBlocked | ToolOutputStore.Error
|
||||
| LLMError
|
||||
| SessionRunnerModel.Error
|
||||
| MessageDecodeError
|
||||
| StepFailedError
|
||||
| UserInterruptedError
|
||||
| SystemContext.InitializationBlocked
|
||||
| ToolOutputStore.Error
|
||||
|
||||
/** Runs one local continuation from already-recorded Session history. */
|
||||
export interface Interface {
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
isContextOverflowFailure,
|
||||
type ProviderErrorEvent,
|
||||
} from "@opencode-ai/llm"
|
||||
import { Cause, DateTime, Effect, Exit, FiberSet, Layer, Option, Semaphore, Stream } from "effect"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { Cause, Effect, Exit, Fiber, FiberSet, Layer, Option, Semaphore, Stream } from "effect"
|
||||
import { AgentV2 } from "../../agent"
|
||||
import { Config } from "../../config"
|
||||
import { Database } from "../../database/database"
|
||||
@@ -31,6 +32,7 @@ import { SessionCompaction } from "../compaction"
|
||||
import { SessionEvent } from "../event"
|
||||
import { SessionHistory } from "../history"
|
||||
import { SessionInput } from "../input"
|
||||
import { SessionMessage } from "../message"
|
||||
import { SessionSchema } from "../schema"
|
||||
import { SessionStore } from "../store"
|
||||
import { SessionTitle } from "../title"
|
||||
@@ -43,6 +45,9 @@ import { SessionRunnerSystemPrompt } from "./system-prompt"
|
||||
import { Snapshot } from "../../snapshot"
|
||||
import { makeLocationNode } from "../../effect/app-node"
|
||||
import { llmClient } from "../../effect/app-node-platform"
|
||||
import { StepFailedError, UserInterruptedError } from "../error"
|
||||
import { toSessionError } from "../to-session-error"
|
||||
import { SessionRunnerRetry } from "./retry"
|
||||
|
||||
/**
|
||||
* Runs one durable coding-agent Session until it settles.
|
||||
@@ -53,10 +58,10 @@ import { llmClient } from "../../effect/app-node-platform"
|
||||
* - Session ownership and controls
|
||||
* - [x] Coordinate one local active drain per Session; explicit resumes join and prompt wakeups coalesce.
|
||||
* - [ ] Replace local ownership with durable multi-node ownership when clustered.
|
||||
* - [ ] Mark busy, retrying, idle, interrupted, or terminal-failure status durably.
|
||||
* - [x] Publish durable historical execution lifecycle and bounded retry observations.
|
||||
* - [ ] Honor interruption and reject stale work after runtime attachment replacement.
|
||||
* - [x] Honor optional agent step limits.
|
||||
* - [ ] Bound provider retries and repeated identical tool calls.
|
||||
* - [ ] Bound repeated identical tool calls (provider retries are bounded).
|
||||
*
|
||||
* - Runtime context assembly
|
||||
* - Track V1 runtime-context parity canonically in `specs/v2/session.md`.
|
||||
@@ -65,7 +70,7 @@ import { llmClient } from "../../effect/app-node-platform"
|
||||
* - [x] Translate every projected V2 Session message variant into canonical
|
||||
* `@opencode-ai/llm` messages.
|
||||
* - [ ] Resolve policy-filtered built-in, MCP, plugin, and structured-output tool definitions.
|
||||
* - [x] Stream exactly one `llm.stream(request)` physical attempt.
|
||||
* - [x] Stream exactly one `llm.stream(request)` call per attempt.
|
||||
* - [x] Persist assistant text and usage events incrementally as they arrive.
|
||||
* - [ ] Persist snapshots, patches, and retry notices incrementally as they arrive.
|
||||
* - [x] Persist reasoning, provider errors, and tool-call events incrementally as they arrive.
|
||||
@@ -86,7 +91,7 @@ import { llmClient } from "../../effect/app-node-platform"
|
||||
* - [ ] Coalesce streamed deltas and add covering projected-history indexes.
|
||||
* - [ ] Update title, summaries, compaction state, and cleanup in bounded background work.
|
||||
*
|
||||
* Use `llm.stream(request)` for each physical attempt. Keep tool execution and continuation here.
|
||||
* Use `llm.stream(request)` for each attempt. Keep tool execution and continuation here.
|
||||
* Durable continuation recovery remains a separate future slice with an explicit retry policy.
|
||||
*
|
||||
* The current slice loads V2 history, translates it, resolves a model through a core service, and persists one
|
||||
@@ -136,19 +141,13 @@ const layer = Layer.effect(
|
||||
sessionID,
|
||||
assistantMessageID: message.id,
|
||||
callID: tool.id,
|
||||
error: { type: "unknown", message: "Tool execution interrupted" },
|
||||
provider: {
|
||||
executed: tool.provider?.executed === true,
|
||||
...(tool.provider?.metadata === undefined ? {} : { metadata: tool.provider.metadata }),
|
||||
},
|
||||
error: { type: "tool.stale", message: `Tool execution interrupted: ${tool.name}` },
|
||||
executed: tool.executed === true,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const awaitToolFibers = (fibers: FiberSet.FiberSet<void, ToolOutputStore.Error>) =>
|
||||
Effect.raceFirst(FiberSet.join(fibers), FiberSet.awaitEmpty(fibers))
|
||||
|
||||
// Match V1: dismissing a question halts the loop instead of becoming model-facing tool output.
|
||||
const isQuestionRejected = (cause: Cause.Cause<unknown>) =>
|
||||
cause.reasons.some((reason) => Cause.isDieReason(reason) && reason.defect instanceof QuestionV2.RejectedError)
|
||||
@@ -171,6 +170,7 @@ const layer = Layer.effect(
|
||||
promotion: SessionInput.Delivery | undefined,
|
||||
step: number,
|
||||
recoverOverflow?: typeof compaction.compactAfterOverflow,
|
||||
assistantMessageID?: SessionMessage.ID,
|
||||
) {
|
||||
const session = yield* getSession(sessionID)
|
||||
if (session.location.directory !== location.directory || session.location.workspaceID !== location.workspaceID)
|
||||
@@ -184,7 +184,8 @@ const layer = Layer.effect(
|
||||
loadSystemContext(agent, session.id),
|
||||
session.id,
|
||||
)
|
||||
const toolFibers = yield* FiberSet.make<void, ToolOutputStore.Error>()
|
||||
const toolFibers = yield* FiberSet.make<void, ToolOutputStore.Error | UserInterruptedError>()
|
||||
const ownedToolFibers: Array<Fiber.Fiber<void, ToolOutputStore.Error | UserInterruptedError>> = []
|
||||
let needsContinuation = false
|
||||
let currentStep = step
|
||||
if (promotion) {
|
||||
@@ -219,7 +220,10 @@ const layer = Layer.effect(
|
||||
toolChoice: isLastStep ? "none" : undefined,
|
||||
})
|
||||
// Automatic compaction completed; rebuild the request from compacted history.
|
||||
if (yield* compaction.compactIfNeeded({ sessionID: session.id, messages: context, request }))
|
||||
if (
|
||||
!(yield* SessionInput.pendingCompaction(db, session.id)) &&
|
||||
(yield* compaction.compactIfNeeded({ sessionID: session.id, messages: context, request }))
|
||||
)
|
||||
return { _tag: "RestartAfterCompaction", step: currentStep } as const
|
||||
const startSnapshot = yield* snapshots.capture()
|
||||
const publisher = createLLMEventPublisher(events, {
|
||||
@@ -228,21 +232,23 @@ const layer = Layer.effect(
|
||||
// The selected catalog identity, not model.id: route-level ids are provider API
|
||||
// model ids (for example gpt-5.5-fast resolves to api id gpt-5.5).
|
||||
model: resolved.ref,
|
||||
provider: model.provider,
|
||||
snapshot: startSnapshot,
|
||||
assistantMessageID,
|
||||
})
|
||||
const publication = Semaphore.makeUnsafe(1)
|
||||
// Durable publishes are serialized so tool fibers and step settlement never interleave
|
||||
// mid-event.
|
||||
const serialized = <A, E, R>(effect: Effect.Effect<A, E, R>) => publication.withPermit(effect)
|
||||
const publish = (event: LLMEvent, outputPaths: ReadonlyArray<string> = []) =>
|
||||
serialized(publisher.publish(event, outputPaths))
|
||||
const publish = (event: LLMEvent, outputPaths: ReadonlyArray<string> = [], error?: SessionError.Error) =>
|
||||
serialized(publisher.publish(event, outputPaths, error))
|
||||
let overflowFailure: ProviderErrorEvent | undefined
|
||||
const providerStream = llm.stream(request).pipe(
|
||||
Stream.runForEach((event) =>
|
||||
Effect.gen(function* () {
|
||||
if (overflowFailure || publisher.hasProviderError()) return
|
||||
if (LLMEvent.is.providerError(event)) {
|
||||
if (isContextOverflowFailure(event) && !publisher.hasAssistantStarted()) {
|
||||
if (isContextOverflowFailure(event) && !publisher.hasRetryEvidence()) {
|
||||
overflowFailure = event
|
||||
return
|
||||
}
|
||||
@@ -250,33 +256,49 @@ const layer = Layer.effect(
|
||||
yield* publish(event)
|
||||
if (event.type !== "tool-call" || event.providerExecuted) return
|
||||
if (!toolMaterialization) {
|
||||
yield* serialized(publisher.failUnsettledTools("Tools are disabled after the maximum agent steps"))
|
||||
yield* serialized(
|
||||
publisher.failUnsettledTools({
|
||||
type: "tool.execution",
|
||||
message: "Tools are disabled after the maximum agent steps",
|
||||
}),
|
||||
)
|
||||
return
|
||||
}
|
||||
needsContinuation = true
|
||||
const assistantMessageID = yield* publisher.assistantMessageID(event.id)
|
||||
yield* Effect.uninterruptibleMask((restore) =>
|
||||
restore(
|
||||
toolMaterialization.settle({
|
||||
sessionID: session.id,
|
||||
agent: agent.id,
|
||||
assistantMessageID,
|
||||
call: event,
|
||||
}),
|
||||
).pipe(
|
||||
Effect.flatMap((settlement) =>
|
||||
publish(
|
||||
LLMEvent.toolResult({
|
||||
id: event.id,
|
||||
name: event.name,
|
||||
result: settlement.result,
|
||||
output: settlement.output,
|
||||
}),
|
||||
settlement.outputPaths ?? [],
|
||||
ownedToolFibers.push(
|
||||
yield* Effect.uninterruptibleMask((restore) =>
|
||||
restore(
|
||||
toolMaterialization.settle({
|
||||
sessionID: session.id,
|
||||
agent: agent.id,
|
||||
assistantMessageID,
|
||||
call: event,
|
||||
}),
|
||||
).pipe(
|
||||
Effect.flatMap((settlement) =>
|
||||
publish(
|
||||
LLMEvent.toolResult({
|
||||
id: event.id,
|
||||
name: event.name,
|
||||
result: settlement.result,
|
||||
output: settlement.output,
|
||||
}),
|
||||
settlement.outputPaths ?? [],
|
||||
settlement.error,
|
||||
).pipe(
|
||||
Effect.andThen(
|
||||
settlement.error?.type === "permission.rejected"
|
||||
? serialized(publisher.failAssistant(settlement.error)).pipe(
|
||||
Effect.andThen(Effect.fail(new UserInterruptedError())),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
).pipe(FiberSet.run(toolFibers))
|
||||
).pipe(FiberSet.run(toolFibers)),
|
||||
)
|
||||
}),
|
||||
),
|
||||
Effect.ensuring(serialized(publisher.flush())),
|
||||
@@ -319,65 +341,118 @@ const layer = Layer.effect(
|
||||
// restart the step instead of surfacing the provider error.
|
||||
if (
|
||||
recoverOverflow &&
|
||||
!publisher.hasAssistantStarted() &&
|
||||
!publisher.hasRetryEvidence() &&
|
||||
isContextOverflowFailure(overflowFailure ?? streamFailure) &&
|
||||
(yield* restore(recoverOverflow({ sessionID: session.id, messages: context, request })))
|
||||
)
|
||||
return { _tag: "RestartAfterOverflowCompaction", step: currentStep } as const
|
||||
|
||||
// An unrecovered held-back overflow becomes the step's durable provider error. A
|
||||
// thrown LLM failure fails hosted tool calls and the assistant unless a provider
|
||||
// error was already recorded from the stream.
|
||||
// thrown LLM failure records the assistant failure unless a provider error was
|
||||
// already recorded from the stream. Terminal publication waits for owned tools.
|
||||
if (overflowFailure) yield* publish(overflowFailure)
|
||||
const llmFailure = streamFailure instanceof LLMError ? streamFailure : undefined
|
||||
if (llmFailure && !publisher.hasProviderError()) {
|
||||
yield* serialized(publisher.failUnsettledTools("Provider did not return a tool result", true))
|
||||
yield* serialized(publisher.failAssistant(llmFailure.reason.message))
|
||||
const error = toSessionError(llmFailure)
|
||||
if (
|
||||
SessionRunnerRetry.isRetryable(llmFailure) &&
|
||||
!publisher.hasRetryEvidence() &&
|
||||
(agent.info?.steps === undefined || currentStep < agent.info.steps)
|
||||
) {
|
||||
return yield* new SessionRunnerRetry.RetryableFailure({
|
||||
cause: llmFailure,
|
||||
assistantMessageID: yield* publisher.startAssistant(),
|
||||
error,
|
||||
step: currentStep,
|
||||
})
|
||||
}
|
||||
yield* serialized(publisher.failAssistant(error))
|
||||
}
|
||||
// Provider error events only arrive from the stream, so the flag is final here.
|
||||
const providerFailed = publisher.hasProviderError()
|
||||
|
||||
// Settle tool fibers: an interrupted stream abandons unstarted tool work first.
|
||||
// Settle every owned tool fiber. FiberSet.join returns on the first failure, so retain
|
||||
// the individual fibers and await all exits before publishing the terminal step event.
|
||||
if (streamInterrupted) yield* FiberSet.clear(toolFibers)
|
||||
const settled = yield* restore(awaitToolFibers(toolFibers)).pipe(Effect.exit)
|
||||
const toolsInterrupted = settled._tag === "Failure" && Cause.hasInterrupts(settled.cause)
|
||||
const questionDismissed = settled._tag === "Failure" && isQuestionRejected(settled.cause)
|
||||
const settled = yield* restore(
|
||||
Effect.forEach(ownedToolFibers, Fiber.await, { concurrency: "unbounded" }),
|
||||
).pipe(Effect.exit)
|
||||
const settledCauses =
|
||||
settled._tag === "Failure"
|
||||
? [settled.cause]
|
||||
: settled.value.flatMap((exit) => (exit._tag === "Failure" ? [exit.cause] : []))
|
||||
const toolsInterrupted = settledCauses.some(Cause.hasInterrupts)
|
||||
const questionDismissed = settledCauses.some(isQuestionRejected)
|
||||
const permissionRejected = settledCauses.some(
|
||||
(cause) => Option.getOrUndefined(Cause.findErrorOption(cause)) instanceof UserInterruptedError,
|
||||
)
|
||||
|
||||
if (questionDismissed || streamInterrupted || toolsInterrupted) {
|
||||
if (questionDismissed || permissionRejected || streamInterrupted || toolsInterrupted) {
|
||||
yield* FiberSet.clear(toolFibers)
|
||||
yield* serialized(publisher.failUnsettledTools("Tool execution interrupted"))
|
||||
yield* serialized(publisher.failAssistant("Step interrupted"))
|
||||
// Match V1: dismissing a question halts the loop like an interruption.
|
||||
if (questionDismissed) return yield* Effect.interrupt
|
||||
yield* serialized(publisher.failUnsettledTools({ type: "aborted", message: "Tool execution interrupted" }))
|
||||
yield* serialized(publisher.failAssistant({ type: "aborted", message: "Step interrupted" }))
|
||||
}
|
||||
// A settled tool fiber failure is one of two things. A defect from a tool
|
||||
// implementation becomes a failed tool call the model can read, and the step still
|
||||
// settles so the model may recover. A typed infrastructure failure (tool output
|
||||
// could not be persisted) also fails the assistant and then fails the drain.
|
||||
const settledFailure = settled._tag === "Failure" && !toolsInterrupted ? settled.cause : undefined
|
||||
const settledFailure = settledCauses.find(
|
||||
(cause) => !Cause.hasInterrupts(cause) && !isQuestionRejected(cause) && !permissionRejected,
|
||||
)
|
||||
const infraError =
|
||||
settledFailure === undefined ? undefined : Option.getOrUndefined(Cause.findErrorOption(settledFailure))
|
||||
if (settledFailure !== undefined) {
|
||||
const failure = infraError ?? Cause.squash(settledFailure)
|
||||
const message = failure instanceof Error ? failure.message : String(failure)
|
||||
yield* serialized(publisher.failUnsettledTools(`Tool execution failed: ${message}`))
|
||||
if (infraError !== undefined)
|
||||
yield* serialized(publisher.failAssistant(`Tool execution failed: ${message}`))
|
||||
const error = toSessionError(failure)
|
||||
yield* serialized(publisher.failUnsettledTools(error))
|
||||
if (infraError !== undefined) yield* serialized(publisher.failAssistant(error))
|
||||
}
|
||||
|
||||
// Fail unresolved calls before the terminal step event. Local calls have joined, so
|
||||
// these sweeps only close calls that could not produce a truthful settlement.
|
||||
if (providerFailed)
|
||||
yield* serialized(publisher.failUnsettledTools({ type: "aborted", message: "Tool execution interrupted" }))
|
||||
if (llmFailure && !providerFailed)
|
||||
yield* serialized(
|
||||
publisher.failUnsettledTools(
|
||||
{
|
||||
type: "tool.result-missing",
|
||||
message: "Provider did not return a tool result",
|
||||
},
|
||||
true,
|
||||
),
|
||||
)
|
||||
const hostedResultMissing =
|
||||
stream._tag === "Success" && !providerFailed
|
||||
? yield* serialized(
|
||||
publisher.failUnsettledTools(
|
||||
{ type: "tool.result-missing", message: "Provider did not return a tool result" },
|
||||
true,
|
||||
),
|
||||
)
|
||||
: false
|
||||
if (hostedResultMissing && !publisher.stepSettlement())
|
||||
yield* serialized(
|
||||
publisher.failAssistant({
|
||||
type: "tool.result-missing",
|
||||
message: "Provider did not return a tool result",
|
||||
}),
|
||||
)
|
||||
|
||||
const stepFailure = publisher.stepFailure()
|
||||
const stepSettlement = publisher.stepSettlement()
|
||||
const stepEndedCleanly =
|
||||
!streamInterrupted && !toolsInterrupted && infraError === undefined && !providerFailed
|
||||
!streamInterrupted && !toolsInterrupted && infraError === undefined && !providerFailed && !stepFailure
|
||||
if (stepSettlement && stepEndedCleanly) yield* publishStepEnd(stepSettlement)
|
||||
// A provider error orphans recorded local calls; a clean stream can still leave
|
||||
// hosted calls without results.
|
||||
if (providerFailed) yield* serialized(publisher.failUnsettledTools("Tool execution interrupted"))
|
||||
if (stream._tag === "Success" && !providerFailed)
|
||||
yield* serialized(publisher.failUnsettledTools("Provider did not return a tool result", true))
|
||||
if (stepFailure) yield* serialized(publisher.publishStepFailure())
|
||||
|
||||
if (stream._tag === "Failure") return yield* Effect.failCause(stream.cause)
|
||||
if (settled._tag === "Failure" && (toolsInterrupted || infraError !== undefined))
|
||||
return yield* Effect.failCause(settled.cause)
|
||||
// Match V1: dismissing a question halts the loop like an interruption.
|
||||
if (questionDismissed || permissionRejected) return yield* new UserInterruptedError()
|
||||
if ((toolsInterrupted || infraError !== undefined) && settledFailure)
|
||||
return yield* Effect.failCause(settledFailure)
|
||||
if (toolsInterrupted && settled._tag === "Failure") return yield* Effect.failCause(settled.cause)
|
||||
if (stepFailure) return yield* new StepFailedError({ error: stepFailure })
|
||||
return {
|
||||
_tag: "Completed",
|
||||
needsContinuation: !providerFailed && needsContinuation,
|
||||
@@ -398,8 +473,31 @@ const layer = Layer.effect(
|
||||
let recoverOverflow: typeof compaction.compactAfterOverflow | undefined = compaction.compactAfterOverflow
|
||||
let currentPromotion = promotion
|
||||
let currentStep = step
|
||||
let assistantMessageID: SessionMessage.ID | undefined
|
||||
while (true) {
|
||||
const attempt = yield* attemptStep(sessionID, currentPromotion, currentStep, recoverOverflow)
|
||||
const attempt = yield* Effect.suspend(() =>
|
||||
attemptStep(sessionID, currentPromotion, currentStep, recoverOverflow, assistantMessageID),
|
||||
).pipe(
|
||||
Effect.tapError((error) =>
|
||||
error instanceof SessionRunnerRetry.RetryableFailure
|
||||
? Effect.sync(() => {
|
||||
currentStep = error.step + 1
|
||||
assistantMessageID = error.assistantMessageID
|
||||
currentPromotion = undefined
|
||||
})
|
||||
: Effect.void,
|
||||
),
|
||||
Effect.retryOrElse(SessionRunnerRetry.schedule(events, sessionID), (error) => {
|
||||
if (!(error instanceof SessionRunnerRetry.RetryableFailure)) return Effect.fail(error)
|
||||
return events
|
||||
.publish(SessionEvent.Step.Failed, {
|
||||
sessionID,
|
||||
assistantMessageID: error.assistantMessageID,
|
||||
error: error.error,
|
||||
})
|
||||
.pipe(Effect.andThen(Effect.fail(error.cause)))
|
||||
}),
|
||||
)
|
||||
if (attempt._tag === "Completed") return { needsContinuation: attempt.needsContinuation, step: attempt.step }
|
||||
if (attempt._tag === "RestartAfterOverflowCompaction") recoverOverflow = undefined
|
||||
yield* Effect.yieldNow
|
||||
@@ -408,12 +506,36 @@ const layer = Layer.effect(
|
||||
}
|
||||
})
|
||||
|
||||
// ExecutionSettled is published per execution (busy period) by SessionExecution, not per
|
||||
// drain here.
|
||||
const runPendingCompaction = Effect.fn("SessionRunner.runPendingCompaction")(function* (
|
||||
sessionID: SessionSchema.ID,
|
||||
) {
|
||||
const pending = yield* SessionInput.pendingCompaction(db, sessionID)
|
||||
if (!pending) return false
|
||||
const session = yield* getSession(sessionID)
|
||||
return yield* Effect.uninterruptibleMask((restore) =>
|
||||
Effect.gen(function* () {
|
||||
const compacted = yield* restore(
|
||||
Effect.gen(function* () {
|
||||
return yield* compaction.compactManual({
|
||||
session,
|
||||
messages: yield* store.context(sessionID),
|
||||
})
|
||||
}),
|
||||
).pipe(Effect.exit)
|
||||
if (Exit.isSuccess(compacted) && compacted.value) return true
|
||||
yield* events.publish(SessionEvent.Compaction.Failed, { sessionID })
|
||||
if (Exit.isFailure(compacted)) return yield* Effect.failCause(compacted.cause)
|
||||
return true
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
// Execution lifecycle is published per busy period by SessionExecution, not per drain here.
|
||||
const drain = Effect.fn("SessionRunner.drain")(function* (input: {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly force: boolean
|
||||
}) {
|
||||
yield* runPendingCompaction(input.sessionID)
|
||||
const hasSteer = yield* SessionInput.hasPending(db, input.sessionID, "steer")
|
||||
const hasQueue = hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue")
|
||||
if (!input.force && !hasSteer && !hasQueue) return
|
||||
@@ -437,11 +559,19 @@ const layer = Layer.effect(
|
||||
}
|
||||
needsContinuation = result.needsContinuation
|
||||
step = result.step + 1
|
||||
if (needsContinuation) {
|
||||
promotion = (yield* SessionInput.pendingCompaction(db, input.sessionID)) ? undefined : "steer"
|
||||
continue
|
||||
}
|
||||
yield* runPendingCompaction(input.sessionID)
|
||||
promotion = "steer"
|
||||
if (!needsContinuation) needsContinuation = yield* SessionInput.hasPending(db, input.sessionID, "steer")
|
||||
needsContinuation = yield* SessionInput.hasPending(db, input.sessionID, "steer")
|
||||
}
|
||||
shouldRun = yield* SessionInput.hasPending(db, input.sessionID, "queue")
|
||||
promotion = shouldRun ? "queue" : undefined
|
||||
yield* runPendingCompaction(input.sessionID)
|
||||
const hasSteer = yield* SessionInput.hasPending(db, input.sessionID, "steer")
|
||||
const hasQueue = hasSteer ? false : yield* SessionInput.hasPending(db, input.sessionID, "queue")
|
||||
shouldRun = hasSteer || hasQueue
|
||||
promotion = hasSteer ? "steer" : hasQueue ? "queue" : undefined
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import { ToolOutput, type LLMEvent, type ProviderMetadata, type ToolResultValue, type Usage } from "@opencode-ai/llm"
|
||||
import { DateTime, Effect } from "effect"
|
||||
import { Effect } from "effect"
|
||||
import { EventV2 } from "../../event"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { SessionEvent } from "../event"
|
||||
import { SessionMessage } from "../message"
|
||||
import { SessionSchema } from "../schema"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
|
||||
type Input = {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
readonly agent: string
|
||||
readonly model: ModelV2.Ref
|
||||
readonly provider: string
|
||||
readonly snapshot?: string
|
||||
readonly assistantMessageID?: SessionMessage.ID
|
||||
}
|
||||
|
||||
const safe = (value: number | undefined) => Math.max(0, Number.isFinite(value) ? (value ?? 0) : 0)
|
||||
@@ -41,10 +44,10 @@ const message = (value: unknown) => {
|
||||
|
||||
type SettledOutput =
|
||||
| { readonly structured: Record<string, unknown>; readonly content: ToolOutput["content"] }
|
||||
| { readonly error: { readonly type: "unknown"; readonly message: string } }
|
||||
| { readonly error: SessionError.Error }
|
||||
|
||||
const settledOutput = (value: ToolOutput | undefined, result: ToolResultValue): SettledOutput => {
|
||||
if (result.type === "error") return { error: { type: "unknown", message: message(result.value) } }
|
||||
if (result.type === "error") return { error: { type: "tool.execution", message: message(result.value) } }
|
||||
const settled = value ?? ToolOutput.fromResultValue(result)
|
||||
if (!settled) throw new Error(`Unsupported tool result: ${message(result)}`)
|
||||
return { structured: record(settled.structured), content: settled.content }
|
||||
@@ -61,20 +64,25 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
called: boolean
|
||||
settled: boolean
|
||||
providerExecuted: boolean
|
||||
providerMetadata?: ProviderMetadata
|
||||
}
|
||||
>()
|
||||
const timestamp = DateTime.now
|
||||
let assistantMessageID: SessionMessage.ID | undefined
|
||||
let assistantActive = false
|
||||
let assistantFailed = false
|
||||
let assistantMessageID = input.assistantMessageID
|
||||
let stepStarted = false
|
||||
let stepFailed = false
|
||||
let providerFailed = false
|
||||
let stepSettlement: { readonly finish: string; readonly tokens: ReturnType<typeof tokens> } | undefined
|
||||
let retryEvidence = false
|
||||
let stepFailure: SessionError.Error | undefined
|
||||
let stepSettlement:
|
||||
| {
|
||||
readonly finish: Extract<LLMEvent, { type: "step-finish" }>["reason"]
|
||||
readonly tokens: ReturnType<typeof tokens>
|
||||
}
|
||||
| undefined
|
||||
|
||||
const startAssistant = Effect.fnUntraced(function* () {
|
||||
if (assistantMessageID !== undefined) return assistantMessageID
|
||||
assistantMessageID = SessionMessage.ID.create()
|
||||
assistantActive = true
|
||||
if (stepStarted && assistantMessageID !== undefined) return assistantMessageID
|
||||
assistantMessageID ??= SessionMessage.ID.create()
|
||||
stepStarted = true
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
...input,
|
||||
assistantMessageID,
|
||||
@@ -86,29 +94,34 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
assistantMessageID === undefined
|
||||
? Effect.die(new Error("Tool event before assistant step start"))
|
||||
: Effect.succeed(assistantMessageID)
|
||||
const providerState = (metadata: ProviderMetadata | undefined) => metadata?.[input.provider]
|
||||
|
||||
const fragments = (
|
||||
name: string,
|
||||
ended: (id: string, value: string, providerMetadata?: ProviderMetadata) => Effect.Effect<void>,
|
||||
ended: (id: string, value: string, ordinal: number, state?: Record<string, unknown>) => Effect.Effect<void>,
|
||||
single = false,
|
||||
) => {
|
||||
const chunks = new Map<string, string[]>()
|
||||
const chunks = new Map<string, { readonly ordinal: number; readonly values: string[] }>()
|
||||
let nextOrdinal = 0
|
||||
const start = (id: string) =>
|
||||
Effect.suspend(() => {
|
||||
if (chunks.has(id)) return Effect.die(new Error(`Duplicate ${name} start: ${id}`))
|
||||
chunks.set(id, [])
|
||||
return Effect.void
|
||||
if (single && chunks.size > 0) return Effect.die(new Error(`${name} start before end: ${id}`))
|
||||
const ordinal = nextOrdinal++
|
||||
chunks.set(id, { ordinal, values: [] })
|
||||
return Effect.succeed(ordinal)
|
||||
})
|
||||
const append = (id: string, value: string) =>
|
||||
Effect.suspend(() => {
|
||||
const current = chunks.get(id)
|
||||
if (!current) return Effect.die(new Error(`${name} delta before start: ${id}`))
|
||||
current.push(value)
|
||||
return Effect.void
|
||||
current.values.push(value)
|
||||
return Effect.succeed(current.ordinal)
|
||||
})
|
||||
const end = Effect.fnUntraced(function* (id: string, providerMetadata?: ProviderMetadata) {
|
||||
const end = Effect.fnUntraced(function* (id: string, state?: Record<string, unknown>) {
|
||||
const current = chunks.get(id)
|
||||
if (!current) return yield* Effect.die(new Error(`${name} end before start: ${id}`))
|
||||
yield* ended(id, current.join(""), providerMetadata)
|
||||
yield* ended(id, current.values.join(""), current.ordinal, state)
|
||||
chunks.delete(id)
|
||||
})
|
||||
const flush = Effect.fnUntraced(function* () {
|
||||
@@ -117,26 +130,32 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
return { start, append, end, flush }
|
||||
}
|
||||
|
||||
const text = fragments("text", (textID, value) =>
|
||||
Effect.gen(function* () {
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
textID,
|
||||
text: value,
|
||||
})
|
||||
}),
|
||||
const text = fragments(
|
||||
"text",
|
||||
(_textID, value, ordinal) =>
|
||||
Effect.gen(function* () {
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
ordinal,
|
||||
text: value,
|
||||
})
|
||||
}),
|
||||
true,
|
||||
)
|
||||
const reasoning = fragments("reasoning", (reasoningID, value, providerMetadata) =>
|
||||
Effect.gen(function* () {
|
||||
yield* events.publish(SessionEvent.Reasoning.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
reasoningID,
|
||||
text: value,
|
||||
providerMetadata,
|
||||
})
|
||||
}),
|
||||
const reasoning = fragments(
|
||||
"reasoning",
|
||||
(_reasoningID, value, ordinal, state) =>
|
||||
Effect.gen(function* () {
|
||||
yield* events.publish(SessionEvent.Reasoning.Ended, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
ordinal,
|
||||
text: value,
|
||||
state,
|
||||
})
|
||||
}),
|
||||
true,
|
||||
)
|
||||
const toolInput = fragments("tool input", (callID, value) =>
|
||||
Effect.gen(function* () {
|
||||
@@ -191,37 +210,41 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
yield* flushFragments()
|
||||
})
|
||||
|
||||
const failAssistant = Effect.fnUntraced(function* (message: string) {
|
||||
if (assistantFailed) return
|
||||
const failAssistant = Effect.fnUntraced(function* (error: SessionError.Error, replace = false) {
|
||||
yield* flush()
|
||||
yield* startAssistant()
|
||||
if (replace || stepFailure === undefined) stepFailure = error
|
||||
})
|
||||
|
||||
const publishStepFailure = Effect.fnUntraced(function* () {
|
||||
if (stepFailed || stepFailure === undefined) return
|
||||
const assistantMessageID = yield* startAssistant()
|
||||
assistantActive = false
|
||||
assistantFailed = true
|
||||
stepFailed = true
|
||||
yield* events.publish(SessionEvent.Step.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID,
|
||||
error: { type: "unknown", message },
|
||||
error: stepFailure,
|
||||
})
|
||||
})
|
||||
|
||||
const failUnsettledTools = Effect.fn("SessionRunner.failUnsettledTools")(function* (
|
||||
message: string,
|
||||
error: SessionError.Error,
|
||||
hostedOnly = false,
|
||||
) {
|
||||
let failed = false
|
||||
for (const [callID, tool] of tools) {
|
||||
if (tool.settled || (hostedOnly && !tool.providerExecuted)) continue
|
||||
tool.settled = true
|
||||
failed = true
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID,
|
||||
error: { type: "unknown", message },
|
||||
provider: {
|
||||
executed: tool.providerExecuted,
|
||||
...(tool.providerMetadata === undefined ? {} : { metadata: tool.providerMetadata }),
|
||||
},
|
||||
error,
|
||||
executed: tool.providerExecuted,
|
||||
})
|
||||
}
|
||||
return failed
|
||||
})
|
||||
|
||||
const assistantMessageIDForTool = (callID: string) => {
|
||||
@@ -232,24 +255,27 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
const publish = Effect.fn("SessionRunner.publishLLMEvent")(function* (
|
||||
event: LLMEvent,
|
||||
outputPaths: ReadonlyArray<string> = [],
|
||||
error?: SessionError.Error,
|
||||
) {
|
||||
switch (event.type) {
|
||||
case "step-start":
|
||||
yield* startAssistant()
|
||||
return
|
||||
case "text-start":
|
||||
yield* text.start(event.id)
|
||||
retryEvidence = true
|
||||
const startedTextOrdinal = yield* text.start(event.id)
|
||||
yield* events.publish(SessionEvent.Text.Started, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* startAssistant(),
|
||||
textID: event.id,
|
||||
ordinal: startedTextOrdinal,
|
||||
})
|
||||
return
|
||||
case "text-delta":
|
||||
yield* text.append(event.id, event.text)
|
||||
const deltaTextOrdinal = yield* text.append(event.id, event.text)
|
||||
yield* events.publish(SessionEvent.Text.Delta, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
textID: event.id,
|
||||
ordinal: deltaTextOrdinal,
|
||||
delta: event.text,
|
||||
})
|
||||
return
|
||||
@@ -257,27 +283,29 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
yield* text.end(event.id)
|
||||
return
|
||||
case "reasoning-start":
|
||||
yield* reasoning.start(event.id)
|
||||
retryEvidence = true
|
||||
const startedReasoningOrdinal = yield* reasoning.start(event.id)
|
||||
yield* events.publish(SessionEvent.Reasoning.Started, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* startAssistant(),
|
||||
reasoningID: event.id,
|
||||
providerMetadata: event.providerMetadata,
|
||||
ordinal: startedReasoningOrdinal,
|
||||
state: providerState(event.providerMetadata),
|
||||
})
|
||||
return
|
||||
case "reasoning-delta":
|
||||
yield* reasoning.append(event.id, event.text)
|
||||
const deltaReasoningOrdinal = yield* reasoning.append(event.id, event.text)
|
||||
yield* events.publish(SessionEvent.Reasoning.Delta, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: yield* currentAssistantMessageID(),
|
||||
reasoningID: event.id,
|
||||
ordinal: deltaReasoningOrdinal,
|
||||
delta: event.text,
|
||||
})
|
||||
return
|
||||
case "reasoning-end":
|
||||
yield* reasoning.end(event.id, event.providerMetadata)
|
||||
yield* reasoning.end(event.id, providerState(event.providerMetadata))
|
||||
return
|
||||
case "tool-input-start":
|
||||
retryEvidence = true
|
||||
yield* startToolInput(event)
|
||||
return
|
||||
case "tool-input-delta": {
|
||||
@@ -299,6 +327,7 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
yield* endToolInput(event)
|
||||
return
|
||||
case "tool-call": {
|
||||
retryEvidence = true
|
||||
if (!tools.has(event.id)) yield* startToolInput(event)
|
||||
const tool = tools.get(event.id)!
|
||||
if (!tool.inputEnded) yield* endToolInput(event)
|
||||
@@ -307,21 +336,19 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
if (tool.called) return yield* Effect.die(new Error(`Duplicate tool call: ${event.id}`))
|
||||
tool.called = true
|
||||
tool.providerExecuted = event.providerExecuted === true
|
||||
tool.providerMetadata = event.providerMetadata
|
||||
const state = providerState(event.providerMetadata)
|
||||
yield* events.publish(SessionEvent.Tool.Called, {
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID: event.id,
|
||||
tool: event.name,
|
||||
input: record(event.input),
|
||||
provider: {
|
||||
executed: tool.providerExecuted,
|
||||
...(event.providerMetadata === undefined ? {} : { metadata: event.providerMetadata }),
|
||||
},
|
||||
executed: tool.providerExecuted,
|
||||
state,
|
||||
})
|
||||
return
|
||||
}
|
||||
case "tool-result": {
|
||||
retryEvidence = true
|
||||
const tool = tools.get(event.id)
|
||||
if (!tool?.called) return yield* Effect.die(new Error(`Tool result before call: ${event.id}`))
|
||||
if (tool.name !== event.name)
|
||||
@@ -331,11 +358,9 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
return yield* Effect.die(new Error(`Duplicate tool result: ${event.id}`))
|
||||
}
|
||||
tool.settled = true
|
||||
const result = settledOutput(event.output, event.result)
|
||||
const provider = {
|
||||
executed: event.providerExecuted === true || tool.providerExecuted,
|
||||
...(event.providerMetadata === undefined ? {} : { metadata: event.providerMetadata }),
|
||||
}
|
||||
const result = error ? { error } : settledOutput(event.output, event.result)
|
||||
const executed = event.providerExecuted === true || tool.providerExecuted
|
||||
const resultState = providerState(event.providerMetadata)
|
||||
if ("error" in result) {
|
||||
yield* events.publish(SessionEvent.Tool.Failed, {
|
||||
sessionID: input.sessionID,
|
||||
@@ -343,7 +368,8 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
callID: event.id,
|
||||
error: result.error,
|
||||
result: event.result,
|
||||
provider,
|
||||
executed,
|
||||
resultState,
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -353,12 +379,14 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
callID: event.id,
|
||||
...result,
|
||||
outputPaths,
|
||||
...(provider.executed ? { result: event.result } : {}),
|
||||
provider,
|
||||
...(executed ? { result: event.result } : {}),
|
||||
executed,
|
||||
resultState,
|
||||
})
|
||||
return
|
||||
}
|
||||
case "tool-error": {
|
||||
retryEvidence = true
|
||||
const tool = tools.get(event.id)
|
||||
if (!tool?.called) return yield* Effect.die(new Error(`Tool error before call: ${event.id}`))
|
||||
if (tool.name !== event.name)
|
||||
@@ -369,25 +397,30 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
sessionID: input.sessionID,
|
||||
assistantMessageID: tool.assistantMessageID,
|
||||
callID: event.id,
|
||||
error: { type: "unknown", message: event.message },
|
||||
provider: {
|
||||
executed: tool.providerExecuted,
|
||||
...(event.providerMetadata === undefined ? {} : { metadata: event.providerMetadata }),
|
||||
},
|
||||
error:
|
||||
event.message === `Unknown tool: ${event.name}`
|
||||
? { type: "tool.unknown", message: event.message }
|
||||
: { type: "tool.execution", message: event.message },
|
||||
executed: tool.providerExecuted,
|
||||
resultState: providerState(event.providerMetadata),
|
||||
})
|
||||
return
|
||||
}
|
||||
case "step-finish":
|
||||
yield* flush()
|
||||
assistantActive = false
|
||||
if (stepSettlement) return yield* Effect.die(new Error("Duplicate step finish"))
|
||||
if (event.reason === "content-filter") {
|
||||
providerFailed = true
|
||||
yield* failAssistant({ type: "provider.content-filter", message: "Provider blocked the response" }, true)
|
||||
return
|
||||
}
|
||||
stepSettlement = { finish: event.reason, tokens: tokens(event.usage) }
|
||||
return
|
||||
case "finish":
|
||||
return
|
||||
case "provider-error":
|
||||
providerFailed = true
|
||||
yield* failAssistant(event.message)
|
||||
yield* failAssistant({ type: "provider.unknown", message: event.message }, true)
|
||||
return
|
||||
}
|
||||
})
|
||||
@@ -396,10 +429,11 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
|
||||
publish,
|
||||
flush,
|
||||
failAssistant,
|
||||
publishStepFailure,
|
||||
failUnsettledTools,
|
||||
hasActiveAssistant: () => assistantActive,
|
||||
hasAssistantStarted: () => assistantMessageID !== undefined,
|
||||
hasProviderError: () => providerFailed,
|
||||
hasRetryEvidence: () => retryEvidence,
|
||||
stepFailure: () => stepFailure,
|
||||
stepSettlement: () => stepSettlement,
|
||||
startAssistant,
|
||||
assistantMessageID: assistantMessageIDForTool,
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
export * as SessionRunnerRetry from "./retry"
|
||||
|
||||
import { LLMError } from "@opencode-ai/llm"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { Data, Duration, Effect, Schedule } from "effect"
|
||||
import { EventV2 } from "../../event"
|
||||
import { SessionEvent } from "../event"
|
||||
import { SessionMessage } from "../message"
|
||||
import { SessionSchema } from "../schema"
|
||||
import type { SessionRunner } from "./index"
|
||||
|
||||
export class RetryableFailure extends Data.TaggedError("SessionRunner.RetryableFailure")<{
|
||||
readonly cause: LLMError
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly error: SessionError.Error
|
||||
readonly step: number
|
||||
}> {}
|
||||
|
||||
export function isRetryable(error: LLMError) {
|
||||
switch (error.reason._tag) {
|
||||
case "RateLimit":
|
||||
case "ProviderInternal":
|
||||
case "Transport":
|
||||
return true
|
||||
case "Authentication":
|
||||
case "QuotaExceeded":
|
||||
case "ContentPolicy":
|
||||
case "InvalidProviderOutput":
|
||||
case "InvalidRequest":
|
||||
case "NoRoute":
|
||||
case "UnknownProvider":
|
||||
return false
|
||||
default: {
|
||||
const exhaustive: never = error.reason
|
||||
return exhaustive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const retryAfter = (failure: RetryableFailure) => {
|
||||
if (failure.cause.reason._tag === "RateLimit" || failure.cause.reason._tag === "ProviderInternal")
|
||||
return failure.cause.reason.retryAfterMs
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const schedule = (events: EventV2.Interface, sessionID: SessionSchema.ID) =>
|
||||
Schedule.exponential("2 seconds").pipe(
|
||||
Schedule.take(4),
|
||||
Schedule.setInputType<RetryableFailure | SessionRunner.RunError>(),
|
||||
Schedule.passthrough,
|
||||
Schedule.while(({ input }) => input instanceof RetryableFailure),
|
||||
Schedule.modifyDelay((failure, delay) => {
|
||||
const minimum = failure instanceof RetryableFailure ? retryAfter(failure) : undefined
|
||||
return Effect.succeed(minimum === undefined ? delay : Duration.max(delay, Duration.millis(minimum)))
|
||||
}),
|
||||
Schedule.tap((metadata) =>
|
||||
metadata.input instanceof RetryableFailure
|
||||
? events.publish(SessionEvent.RetryScheduled, {
|
||||
sessionID,
|
||||
assistantMessageID: metadata.input.assistantMessageID,
|
||||
attempt: metadata.attempt + 1,
|
||||
at: metadata.now + Duration.toMillis(metadata.duration),
|
||||
error: metadata.input.error,
|
||||
})
|
||||
: Effect.void,
|
||||
),
|
||||
)
|
||||
@@ -21,6 +21,11 @@ const media = (file: FileAttachment): ContentPart => ({
|
||||
|
||||
const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString)
|
||||
|
||||
const providerMetadata = (
|
||||
provider: string,
|
||||
state: Record<string, unknown> | undefined,
|
||||
): ProviderMetadata | undefined => (state === undefined ? undefined : { [provider]: state })
|
||||
|
||||
const toolInput = (tool: SessionMessage.AssistantTool) =>
|
||||
tool.state.status === "pending"
|
||||
? Option.getOrElse(decodeToolInput(tool.state.input), () => tool.state.input)
|
||||
@@ -31,7 +36,7 @@ const toolCall = (tool: SessionMessage.AssistantTool, providerMetadata: Provider
|
||||
id: tool.id,
|
||||
name: tool.name,
|
||||
input: toolInput(tool),
|
||||
providerExecuted: tool.provider?.executed,
|
||||
providerExecuted: tool.executed,
|
||||
providerMetadata,
|
||||
})
|
||||
|
||||
@@ -40,14 +45,14 @@ const toolResult = (tool: SessionMessage.AssistantTool, providerMetadata: Provid
|
||||
// TODO: Materialize remote and managed URIs before provider-history lowering.
|
||||
// ToolOutput.toResultValue rejects unresolved URIs rather than treating them as media bytes.
|
||||
const result =
|
||||
tool.provider?.executed === true && tool.state.result !== undefined
|
||||
tool.executed === true && tool.state.result !== undefined
|
||||
? tool.state.result
|
||||
: ToolOutput.toResultValue({ structured: tool.state.structured, content: tool.state.content })
|
||||
return ToolResultPart.make({
|
||||
id: tool.id,
|
||||
name: tool.name,
|
||||
result,
|
||||
providerExecuted: tool.provider?.executed,
|
||||
providerExecuted: tool.executed,
|
||||
providerMetadata,
|
||||
})
|
||||
}
|
||||
@@ -56,11 +61,11 @@ const toolResult = (tool: SessionMessage.AssistantTool, providerMetadata: Provid
|
||||
id: tool.id,
|
||||
name: tool.name,
|
||||
result:
|
||||
tool.provider?.executed === true && tool.state.result !== undefined
|
||||
tool.executed === true && tool.state.result !== undefined
|
||||
? tool.state.result
|
||||
: { error: tool.state.error, content: tool.state.content, structured: tool.state.structured },
|
||||
resultType: "error",
|
||||
providerExecuted: tool.provider?.executed,
|
||||
providerExecuted: tool.executed,
|
||||
providerMetadata,
|
||||
})
|
||||
}
|
||||
@@ -78,17 +83,22 @@ const assistant = (message: SessionMessage.Assistant, model: Model) => {
|
||||
{
|
||||
type: "reasoning",
|
||||
text: item.text,
|
||||
providerMetadata: reuseProviderMetadata ? item.providerMetadata : undefined,
|
||||
providerMetadata: reuseProviderMetadata ? providerMetadata(model.provider, item.state) : undefined,
|
||||
},
|
||||
]
|
||||
: item.text.length > 0
|
||||
? [{ type: "text", text: item.text }]
|
||||
: []
|
||||
const call = toolCall(item, reuseProviderMetadata ? item.provider?.metadata : undefined)
|
||||
if (item.provider?.executed !== true) return [call]
|
||||
const call = toolCall(
|
||||
item,
|
||||
reuseProviderMetadata ? providerMetadata(model.provider, item.providerState) : undefined,
|
||||
)
|
||||
if (item.executed !== true) return [call]
|
||||
const result = toolResult(
|
||||
item,
|
||||
reuseProviderMetadata ? (item.provider.resultMetadata ?? item.provider.metadata) : undefined,
|
||||
reuseProviderMetadata
|
||||
? providerMetadata(model.provider, item.providerResultState ?? item.providerState)
|
||||
: undefined,
|
||||
)
|
||||
return result ? [call, result] : [call]
|
||||
})
|
||||
@@ -98,9 +108,14 @@ const assistant = (message: SessionMessage.Assistant, model: Model) => {
|
||||
return part.text !== "" || (part.providerMetadata !== undefined && Object.keys(part.providerMetadata).length > 0)
|
||||
})
|
||||
const results = message.content
|
||||
.filter((item): item is SessionMessage.AssistantTool => item.type === "tool" && item.provider?.executed !== true)
|
||||
.filter((item): item is SessionMessage.AssistantTool => item.type === "tool" && item.executed !== true)
|
||||
.map((item) =>
|
||||
toolResult(item, reuseProviderMetadata ? (item.provider?.resultMetadata ?? item.provider?.metadata) : undefined),
|
||||
toolResult(
|
||||
item,
|
||||
reuseProviderMetadata
|
||||
? providerMetadata(model.provider, item.providerResultState ?? item.providerState)
|
||||
: undefined,
|
||||
),
|
||||
)
|
||||
.filter((message) => message !== undefined)
|
||||
.map(Message.tool)
|
||||
@@ -146,6 +161,7 @@ function toLLMMessage(message: SessionMessage.Message, model: Model): Message[]
|
||||
case "assistant":
|
||||
return assistant(message, model)
|
||||
case "compaction":
|
||||
if (message.status !== "completed") return []
|
||||
return [
|
||||
Message.make({
|
||||
id: message.id,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from "drizzle-orm/sqlite-core"
|
||||
import { sql } from "drizzle-orm"
|
||||
import { directoryColumn, pathColumn } from "../database/path"
|
||||
import { ProjectTable } from "../project/sql"
|
||||
import type { SessionMessage } from "./message"
|
||||
@@ -146,8 +147,9 @@ export const SessionInputTable = sqliteTable(
|
||||
.$type<SessionSchema.ID>()
|
||||
.notNull()
|
||||
.references(() => SessionTable.id, { onDelete: "cascade" }),
|
||||
prompt: text({ mode: "json" }).notNull().$type<Prompt>(),
|
||||
delivery: text().$type<SessionInput.Delivery>().notNull(),
|
||||
type: text().$type<SessionInput.Entry["type"]>().notNull(),
|
||||
prompt: text({ mode: "json" }).$type<Prompt>(),
|
||||
delivery: text().$type<SessionInput.Delivery>(),
|
||||
admitted_seq: integer().notNull(),
|
||||
promoted_seq: integer(),
|
||||
time_created: integer()
|
||||
@@ -155,12 +157,16 @@ export const SessionInputTable = sqliteTable(
|
||||
.$default(() => Date.now()),
|
||||
},
|
||||
(table) => [
|
||||
index("session_input_session_pending_delivery_seq_idx").on(
|
||||
index("session_input_session_pending_type_delivery_seq_idx").on(
|
||||
table.session_id,
|
||||
table.promoted_seq,
|
||||
table.type,
|
||||
table.delivery,
|
||||
table.admitted_seq,
|
||||
),
|
||||
uniqueIndex("session_input_session_pending_compaction_idx")
|
||||
.on(table.session_id)
|
||||
.where(sql`${table.type} = 'compaction' and ${table.promoted_seq} is null`),
|
||||
uniqueIndex("session_input_session_admitted_seq_idx").on(table.session_id, table.admitted_seq),
|
||||
uniqueIndex("session_input_session_promoted_seq_idx").on(table.session_id, table.promoted_seq),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { LLMError, ToolFailure } from "@opencode-ai/llm"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { PermissionV2 } from "../permission"
|
||||
import { QuestionV2 } from "../question"
|
||||
import { Integration } from "../integration"
|
||||
import { ToolOutputStore } from "../tool-output-store"
|
||||
import { StepFailedError, UserInterruptedError } from "./error"
|
||||
import { SessionRunnerModel } from "./runner/model"
|
||||
|
||||
export function toSessionError(cause: unknown): SessionError.Error {
|
||||
if (cause instanceof LLMError) {
|
||||
switch (cause.reason._tag) {
|
||||
case "RateLimit":
|
||||
return { type: "provider.rate-limit", message: cause.reason.message }
|
||||
case "Authentication":
|
||||
return { type: "provider.auth", message: cause.reason.message }
|
||||
case "QuotaExceeded":
|
||||
return { type: "provider.quota", message: cause.reason.message }
|
||||
case "ContentPolicy":
|
||||
return { type: "provider.content-filter", message: cause.reason.message }
|
||||
case "Transport":
|
||||
return { type: "provider.transport", message: cause.reason.message }
|
||||
case "ProviderInternal":
|
||||
return { type: "provider.internal", message: cause.reason.message }
|
||||
case "InvalidProviderOutput":
|
||||
return { type: "provider.invalid-output", message: cause.reason.message }
|
||||
case "InvalidRequest":
|
||||
return { type: "provider.invalid-request", message: cause.reason.message }
|
||||
case "NoRoute":
|
||||
return { type: "provider.no-route", message: cause.reason.message }
|
||||
case "UnknownProvider":
|
||||
return { type: "provider.unknown", message: cause.reason.message }
|
||||
default: {
|
||||
const exhaustive: never = cause.reason
|
||||
return exhaustive
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cause instanceof PermissionV2.DeniedError || cause instanceof PermissionV2.RejectedError)
|
||||
return { type: "permission.rejected", message: cause.message }
|
||||
if (cause instanceof QuestionV2.RejectedError) return { type: "aborted", message: cause.message }
|
||||
if (cause instanceof ToolFailure)
|
||||
return cause.error === undefined ? { type: "tool.execution", message: cause.message } : toSessionError(cause.error)
|
||||
if (cause instanceof StepFailedError) return cause.error
|
||||
if (cause instanceof UserInterruptedError) return { type: "aborted", message: cause.message }
|
||||
if (
|
||||
cause instanceof SessionRunnerModel.ModelNotSelectedError ||
|
||||
cause instanceof SessionRunnerModel.ModelUnavailableError ||
|
||||
cause instanceof SessionRunnerModel.VariantUnavailableError ||
|
||||
cause instanceof SessionRunnerModel.UnsupportedApiError
|
||||
)
|
||||
return { type: "provider.no-route", message: cause.message }
|
||||
if (cause instanceof Integration.AuthorizationError) return { type: "provider.auth", message: cause.message }
|
||||
if (cause instanceof ToolOutputStore.StorageError) return { type: "unknown", message: cause.message }
|
||||
return { type: "unknown", message: cause instanceof Error ? cause.message : String(cause) }
|
||||
}
|
||||
@@ -73,12 +73,12 @@ export const Plugin = {
|
||||
toModelOutput: ({ output }) => [{ type: "text", text: toModelOutput(output) }],
|
||||
execute: (input, context) => {
|
||||
const applied: Array<typeof Applied.Type> = []
|
||||
const fail = (path: string) => {
|
||||
const fail = (path: string, error?: unknown) => {
|
||||
const prefix =
|
||||
applied.length === 0
|
||||
? `Unable to apply patch at ${path}`
|
||||
: `Patch partially applied before failing at ${path}. Applied: ${applied.map((item) => item.resource).join(", ")}`
|
||||
return new ToolFailure({ message: prefix })
|
||||
return new ToolFailure({ message: prefix, error })
|
||||
}
|
||||
return Effect.gen(function* () {
|
||||
const source = {
|
||||
@@ -150,7 +150,7 @@ export const Plugin = {
|
||||
before,
|
||||
after: update.content,
|
||||
})
|
||||
}).pipe(Effect.mapError(() => fail(hunk.path)))
|
||||
}).pipe(Effect.mapError((error) => fail(hunk.path, error)))
|
||||
}
|
||||
|
||||
const patchFiles = prepared.map(patchFile)
|
||||
@@ -180,11 +180,11 @@ export const Plugin = {
|
||||
content: change.content,
|
||||
})
|
||||
applied.push({ type: change.type, resource: result.resource, target: result.target })
|
||||
}).pipe(Effect.mapError(() => fail(change.path))),
|
||||
}).pipe(Effect.mapError((error) => fail(change.path, error))),
|
||||
{ discard: true },
|
||||
)
|
||||
return { applied, files: patchFiles }
|
||||
}).pipe(Effect.mapError((error) => (error instanceof ToolFailure ? error : fail("patch"))))
|
||||
}).pipe(Effect.mapError((error) => (error instanceof ToolFailure ? error : fail("patch", error))))
|
||||
},
|
||||
}),
|
||||
"edit",
|
||||
|
||||
@@ -111,8 +111,9 @@ export const Plugin = {
|
||||
error instanceof FileMutation.StaleContentError
|
||||
? new ToolFailure({
|
||||
message: "File changed after permission approval. Read it again before editing.",
|
||||
error,
|
||||
})
|
||||
: new ToolFailure({ message: `Unable to edit ${input.path}` }),
|
||||
: new ToolFailure({ message: `Unable to edit ${input.path}`, error }),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { PluginContext } from "@opencode-ai/plugin/v2/effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import path from "path"
|
||||
import { FileSystem } from "../filesystem"
|
||||
import { FSUtil } from "../fs-util"
|
||||
import { Location } from "../location"
|
||||
import { Ripgrep } from "../ripgrep"
|
||||
import { RelativePath } from "../schema"
|
||||
@@ -36,6 +37,7 @@ export const toModelOutput = (output: ModelOutput) => {
|
||||
export const Plugin = {
|
||||
id: "core-glob-tool",
|
||||
effect: Effect.fn("GlobTool.Plugin")(function* (ctx: PluginContext) {
|
||||
const fs = yield* FSUtil.Service
|
||||
const ripgrep = yield* Ripgrep.Service
|
||||
const location = yield* Location.Service
|
||||
const permission = yield* PermissionV2.Service
|
||||
@@ -71,6 +73,13 @@ export const Plugin = {
|
||||
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
||||
})
|
||||
const cwd = path.resolve(location.directory, input.path ?? ".")
|
||||
yield* fs
|
||||
.stat(cwd)
|
||||
.pipe(
|
||||
Effect.catchReason("PlatformError", "NotFound", () =>
|
||||
Effect.fail(new ToolFailure({ message: `Search path does not exist: ${input.path ?? "."}` })),
|
||||
),
|
||||
)
|
||||
return yield* ripgrep
|
||||
.glob({
|
||||
cwd,
|
||||
@@ -88,7 +97,11 @@ export const Plugin = {
|
||||
),
|
||||
)
|
||||
}).pipe(
|
||||
Effect.mapError(() => new ToolFailure({ message: `Unable to find files matching ${input.pattern}` })),
|
||||
Effect.mapError((error) =>
|
||||
error instanceof ToolFailure
|
||||
? error
|
||||
: new ToolFailure({ message: `Unable to find files matching ${input.pattern}`, error }),
|
||||
),
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -91,7 +91,13 @@ export const Plugin = {
|
||||
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
||||
})
|
||||
const target = path.resolve(location.directory, input.path ?? ".")
|
||||
const info = yield* fs.stat(target).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
const info = yield* fs
|
||||
.stat(target)
|
||||
.pipe(
|
||||
Effect.catchReason("PlatformError", "NotFound", () =>
|
||||
Effect.fail(new ToolFailure({ message: `Search path does not exist: ${input.path ?? "."}` })),
|
||||
),
|
||||
)
|
||||
return yield* ripgrep
|
||||
.grep({
|
||||
cwd: info?.type === "Directory" ? target : path.dirname(target),
|
||||
@@ -121,7 +127,13 @@ export const Plugin = {
|
||||
),
|
||||
),
|
||||
)
|
||||
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to grep for ${input.pattern}` }))),
|
||||
}).pipe(
|
||||
Effect.mapError((error) =>
|
||||
error instanceof ToolFailure
|
||||
? error
|
||||
: new ToolFailure({ message: `Unable to grep for ${input.pattern}`, error }),
|
||||
),
|
||||
),
|
||||
}),
|
||||
})
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
@@ -67,7 +67,7 @@ export const Plugin = {
|
||||
source: { type: "tool", messageID: context.assistantMessageID, callID: context.toolCallID },
|
||||
})
|
||||
.pipe(
|
||||
Effect.mapError(() => new ToolFailure({ message: "Permission denied: question" })),
|
||||
Effect.mapError((error) => new ToolFailure({ message: "Permission denied: question", error })),
|
||||
Effect.andThen(
|
||||
question
|
||||
.ask({
|
||||
|
||||
@@ -106,7 +106,9 @@ export const Plugin = {
|
||||
start: type === "directory" ? resolved : dirname(resolved),
|
||||
stop: root,
|
||||
})
|
||||
const candidates = (yield* Effect.forEach(discovered, fs.resolve)).filter((file) => dirname(file) !== root)
|
||||
const candidates = (yield* Effect.forEach(discovered, fs.resolve)).filter(
|
||||
(file) => dirname(file) !== root,
|
||||
)
|
||||
if (candidates.length === 0) return
|
||||
yield* sessionInstructions.load({ sessionID: context.sessionID, paths: candidates })
|
||||
}).pipe(
|
||||
@@ -130,7 +132,7 @@ export const Plugin = {
|
||||
error instanceof Image.SizeError
|
||||
? error.message
|
||||
: `Unable to read ${input.path}`
|
||||
return new ToolFailure({ message })
|
||||
return new ToolFailure({ message, error })
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
@@ -12,6 +12,8 @@ import { definition, permission, registrationEntries, settle, type AnyTool, type
|
||||
import { Tools } from "./tools"
|
||||
import { ToolHooks } from "./hooks"
|
||||
import { makeLocationNode } from "../effect/app-node"
|
||||
import { SessionError } from "@opencode-ai/schema/session-error"
|
||||
import { toSessionError } from "../session/to-session-error"
|
||||
|
||||
export type ExecuteInput = {
|
||||
readonly sessionID: SessionSchema.ID
|
||||
@@ -40,6 +42,7 @@ export interface Settlement {
|
||||
readonly result: ToolResultValue
|
||||
readonly output?: ToolOutput
|
||||
readonly outputPaths?: ReadonlyArray<string>
|
||||
readonly error?: SessionError.Error
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/ToolRegistry") {}
|
||||
@@ -60,9 +63,15 @@ const registryLayer = Layer.effect(
|
||||
type: "error" as const,
|
||||
value: advertised ? `Stale tool call: ${input.call.name}` : `Unknown tool: ${input.call.name}`,
|
||||
},
|
||||
error: advertised
|
||||
? ({ type: "tool.stale", message: `Stale tool call: ${input.call.name}` } as const)
|
||||
: ({ type: "tool.unknown", message: `Unknown tool: ${input.call.name}` } as const),
|
||||
}
|
||||
if (advertised && registration.identity !== advertised)
|
||||
return { result: { type: "error" as const, value: `Stale tool call: ${input.call.name}` } }
|
||||
return {
|
||||
result: { type: "error" as const, value: `Stale tool call: ${input.call.name}` },
|
||||
error: { type: "tool.stale" as const, message: `Stale tool call: ${input.call.name}` },
|
||||
}
|
||||
// Hooks fire only for hosted/local tools; provider-executed calls never reach settleWith.
|
||||
const beforeEvent: ToolHooks.BeforeEvent = {
|
||||
tool: input.call.name,
|
||||
@@ -73,22 +82,33 @@ const registryLayer = Layer.effect(
|
||||
input: input.call.input,
|
||||
}
|
||||
yield* toolHooks.runBefore(beforeEvent)
|
||||
const pending = yield* settle(registration.tool, { ...input.call, input: beforeEvent.input }, {
|
||||
sessionID: input.sessionID,
|
||||
agent: input.agent,
|
||||
assistantMessageID: input.assistantMessageID,
|
||||
toolCallID: input.call.id,
|
||||
}).pipe(
|
||||
const pending = yield* settle(
|
||||
registration.tool,
|
||||
{ ...input.call, input: beforeEvent.input },
|
||||
{
|
||||
sessionID: input.sessionID,
|
||||
agent: input.agent,
|
||||
assistantMessageID: input.assistantMessageID,
|
||||
toolCallID: input.call.id,
|
||||
},
|
||||
).pipe(
|
||||
Effect.map((output) => ({ output })),
|
||||
Effect.catchTag("LLM.ToolFailure", (failure) =>
|
||||
Effect.succeed({ result: { type: "error" as const, value: failure.message } }),
|
||||
Effect.succeed({
|
||||
result: { type: "error" as const, value: failure.message },
|
||||
error: toSessionError(failure),
|
||||
}),
|
||||
),
|
||||
)
|
||||
let settlement: Settlement
|
||||
if ("result" in pending) {
|
||||
settlement = pending
|
||||
} else {
|
||||
const bounded = yield* resources.bound({ sessionID: input.sessionID, toolCallID: input.call.id, output: pending.output })
|
||||
const bounded = yield* resources.bound({
|
||||
sessionID: input.sessionID,
|
||||
toolCallID: input.call.id,
|
||||
output: pending.output,
|
||||
})
|
||||
const result = ToolOutput.toResultValue(bounded.output)
|
||||
settlement =
|
||||
result.type === "error"
|
||||
@@ -115,6 +135,7 @@ const registryLayer = Layer.effect(
|
||||
result: afterEvent.result,
|
||||
...(afterEvent.output !== undefined ? { output: afterEvent.output } : {}),
|
||||
...(afterEvent.outputPaths !== undefined ? { outputPaths: afterEvent.outputPaths } : {}),
|
||||
...(settlement.error !== undefined ? { error: settlement.error } : {}),
|
||||
}
|
||||
})
|
||||
|
||||
@@ -157,7 +178,10 @@ const registryLayer = Layer.effect(
|
||||
settle: (input) => {
|
||||
const registration = registrations.get(input.call.name)
|
||||
if (registration) return settleWith(input, registration.identity)
|
||||
return Effect.succeed({ result: { type: "error", value: `Unknown tool: ${input.call.name}` } })
|
||||
return Effect.succeed({
|
||||
result: { type: "error", value: `Unknown tool: ${input.call.name}` },
|
||||
error: { type: "tool.unknown", message: `Unknown tool: ${input.call.name}` },
|
||||
})
|
||||
},
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -18,8 +18,9 @@ export const DEFAULT_TIMEOUT_MS = 2 * 60 * 1_000
|
||||
export const MAX_TIMEOUT_MS = 10 * 60 * 1_000
|
||||
export const MAX_CAPTURE_BYTES = 1024 * 1024
|
||||
|
||||
const BACKGROUND_STARTED =
|
||||
"The command has not completed; it is now running in the background."
|
||||
const BACKGROUND_STARTED = "The command was moved to the background."
|
||||
const BACKGROUND_INSTRUCTION =
|
||||
"You will be notified automatically when the command finishes. DO NOT sleep, poll, or proactively check on its progress."
|
||||
|
||||
export const Input = Schema.Struct({
|
||||
command: Schema.String.annotate({ description: "Shell command string to execute" }),
|
||||
@@ -54,10 +55,11 @@ const Output = Schema.Struct({
|
||||
type Output = typeof Output.Type
|
||||
|
||||
const modelOutput = (output: Output): string | undefined => {
|
||||
if (output.status === "running") return undefined
|
||||
const warnings = output.warnings?.length
|
||||
? `\n\nWarnings:\n${output.warnings.map((warning) => `- ${warning}`).join("\n")}`
|
||||
: ""
|
||||
if (output.status === "running")
|
||||
return `${warnings.trimStart()}${warnings ? "\n\n" : ""}${BACKGROUND_INSTRUCTION}`
|
||||
if (output.timeout) return `${warnings.trimStart()}${warnings ? "\n\n" : ""}Command timed out before completion.`
|
||||
return `${warnings.trimStart()}${warnings ? "\n\n" : ""}Command exited with code ${output.exit}.`
|
||||
}
|
||||
@@ -245,9 +247,9 @@ export const Plugin = {
|
||||
}
|
||||
}
|
||||
|
||||
const result = yield* runtime.job.block({ id: job.id, sessionID: context.sessionID }).pipe(
|
||||
Effect.onInterrupt(() => runtime.job.cancel(job.id).pipe(Effect.ignore)),
|
||||
)
|
||||
const result = yield* runtime.job
|
||||
.block({ id: job.id, sessionID: context.sessionID })
|
||||
.pipe(Effect.onInterrupt(() => runtime.job.cancel(job.id).pipe(Effect.ignore)))
|
||||
if (result?.type === "backgrounded") {
|
||||
yield* notifyWhenDone(context.sessionID, context.toolCallID, input.command)
|
||||
return {
|
||||
@@ -258,14 +260,19 @@ export const Plugin = {
|
||||
...(warnings.length ? { warnings } : {}),
|
||||
}
|
||||
}
|
||||
if (result?.info.status === "error") return yield* Effect.fail(new Error(result.info.error ?? "Command failed"))
|
||||
if (result?.info.status === "error")
|
||||
return yield* Effect.fail(new Error(result.info.error ?? "Command failed"))
|
||||
if (result?.info.status === "cancelled") return yield* Effect.fail(new Error("Command cancelled"))
|
||||
|
||||
return {
|
||||
...(yield* settleShell()),
|
||||
...(warnings.length ? { warnings } : {}),
|
||||
}
|
||||
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to execute command: ${input.command}` }))),
|
||||
}).pipe(
|
||||
Effect.mapError(
|
||||
(error) => new ToolFailure({ message: `Unable to execute command: ${input.command}`, error }),
|
||||
),
|
||||
),
|
||||
}),
|
||||
})
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
@@ -104,7 +104,9 @@ export const Plugin = {
|
||||
const parent = yield* runtime.session
|
||||
.get(context.sessionID)
|
||||
.pipe(
|
||||
Effect.mapError(() => new ToolFailure({ message: `Parent session not found: ${context.sessionID}` })),
|
||||
Effect.mapError(
|
||||
(error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }),
|
||||
),
|
||||
)
|
||||
const agent = yield* agents.resolve(input.agent)
|
||||
if (agent === undefined) return yield* new ToolFailure({ message: `Unknown agent: ${input.agent}` })
|
||||
@@ -123,7 +125,9 @@ export const Plugin = {
|
||||
// session (V1 deriveSubagentSessionPermission). MVP uses the agent's own permissions.
|
||||
})
|
||||
.pipe(
|
||||
Effect.mapError(() => new ToolFailure({ message: `Parent session not found: ${context.sessionID}` })),
|
||||
Effect.mapError(
|
||||
(error) => new ToolFailure({ message: `Parent session not found: ${context.sessionID}`, error }),
|
||||
),
|
||||
)
|
||||
|
||||
const background = input.background === true
|
||||
|
||||
@@ -46,7 +46,7 @@ export const Plugin = {
|
||||
})
|
||||
yield* todos.update({ sessionID: context.sessionID, todos: input.todos })
|
||||
return { todos: input.todos }
|
||||
}).pipe(Effect.mapError(() => new ToolFailure({ message: "Unable to update todos" }))),
|
||||
}).pipe(Effect.mapError((error) => new ToolFailure({ message: "Unable to update todos", error }))),
|
||||
}),
|
||||
})
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
@@ -170,7 +170,7 @@ export const Plugin = {
|
||||
format: input.format,
|
||||
output,
|
||||
}
|
||||
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to fetch ${input.url}` }))),
|
||||
}).pipe(Effect.mapError((error) => new ToolFailure({ message: `Unable to fetch ${input.url}`, error }))),
|
||||
}),
|
||||
})
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
@@ -243,7 +243,11 @@ export const Plugin = {
|
||||
provider,
|
||||
text: text ?? NO_RESULTS,
|
||||
}
|
||||
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to search the web for ${input.query}` })))
|
||||
}).pipe(
|
||||
Effect.mapError(
|
||||
(error) => new ToolFailure({ message: `Unable to search the web for ${input.query}`, error }),
|
||||
),
|
||||
)
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -83,7 +83,7 @@ export const Plugin = {
|
||||
source,
|
||||
})
|
||||
return yield* files.writeTextPreservingBom({ target, content: input.content })
|
||||
}).pipe(Effect.mapError(() => new ToolFailure({ message: `Unable to write ${input.path}` }))),
|
||||
}).pipe(Effect.mapError((error) => new ToolFailure({ message: `Unable to write ${input.path}`, error }))),
|
||||
}),
|
||||
"edit",
|
||||
),
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Config as ConfigSchema } from "@opencode-ai/schema/config"
|
||||
import { Effect, Schema } from "effect"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { Config } from "@opencode-ai/core/config"
|
||||
import { ConfigExternalPlugin } from "@opencode-ai/core/config/plugin/external"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||
import { PluginHost } from "@opencode-ai/core/plugin/host"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { tmpdir } from "../fixture/tmpdir"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { PluginTestLayer } from "../plugin/fixture"
|
||||
|
||||
@@ -244,49 +240,6 @@ describe("ConfigExternalPlugin", () => {
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("reloads changed plugin source from the same entrypoint", () =>
|
||||
Effect.acquireRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
).pipe(
|
||||
Effect.flatMap((tmp) =>
|
||||
Effect.gen(function* () {
|
||||
const plugins = yield* PluginV2.Service
|
||||
const agents = yield* AgentV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
const fsUtil = yield* FSUtil.Service
|
||||
const location = yield* Location.Service
|
||||
const npm = yield* Npm.Service
|
||||
const host = yield* PluginHost.make(plugins)
|
||||
const plugin = path.join(tmp.path, "plugin.ts")
|
||||
const config = Config.Service.of({
|
||||
entries: () =>
|
||||
Effect.succeed([
|
||||
new Config.Document({
|
||||
type: "document",
|
||||
info: decode({ plugins: [plugin] }),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
|
||||
yield* Effect.promise(() => fs.writeFile(plugin, pluginSource("First source")))
|
||||
yield* ConfigExternalPlugin.Plugin.effect(host).pipe(
|
||||
Effect.provideService(PluginV2.Service, plugins),
|
||||
Effect.provideService(FSUtil.Service, fsUtil),
|
||||
Effect.provideService(Location.Service, location),
|
||||
Effect.provideService(Npm.Service, npm),
|
||||
Effect.provideService(Config.Service, config),
|
||||
)
|
||||
expect((yield* waitForAgent(agents, "hot-reload"))?.description).toBe("First source")
|
||||
|
||||
yield* Effect.promise(() => fs.writeFile(plugin, pluginSource("Second source")))
|
||||
yield* events.publish(ConfigSchema.Event.Updated, {})
|
||||
expect(yield* waitForAgentDescription(agents, "hot-reload", "Second source")).toBe(true)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
const waitForAgent = Effect.fnUntraced(function* (agents: AgentV2.Interface, id: string) {
|
||||
@@ -297,29 +250,3 @@ const waitForAgent = Effect.fnUntraced(function* (agents: AgentV2.Interface, id:
|
||||
}
|
||||
return yield* Effect.die(`Timed out waiting for agent ${id}`)
|
||||
})
|
||||
|
||||
const waitForAgentDescription = Effect.fnUntraced(function* (
|
||||
agents: AgentV2.Interface,
|
||||
id: string,
|
||||
description: string,
|
||||
) {
|
||||
for (let attempt = 0; attempt < 100; attempt++) {
|
||||
if ((yield* agents.get(AgentV2.ID.make(id)))?.description === description) return true
|
||||
yield* Effect.sleep("10 millis")
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
function pluginSource(description: string) {
|
||||
return `export default {
|
||||
id: "source-hot-reload",
|
||||
setup: async (ctx) => {
|
||||
await ctx.agent.transform((agents) => {
|
||||
agents.update("hot-reload", (agent) => {
|
||||
agent.description = ${JSON.stringify(description)}
|
||||
agent.mode = "subagent"
|
||||
})
|
||||
})
|
||||
},
|
||||
}`
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ const decode = Schema.decodeUnknownSync(Config.Info)
|
||||
const document = path.join(import.meta.dir, "opencode.json")
|
||||
|
||||
describe("config plugin reloads", () => {
|
||||
it.live("reloads every config-backed domain", () =>
|
||||
it.live("reloads config-backed domains without reloading external plugins", () =>
|
||||
Effect.gen(function* () {
|
||||
const agents = yield* AgentV2.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
@@ -69,8 +69,7 @@ describe("config plugin reloads", () => {
|
||||
(yield* commands.get("second"))?.description === "Second command" &&
|
||||
(yield* references.list()).some((reference) => reference.name === "second") &&
|
||||
(yield* catalog.provider.get(ProviderV2.ID.make("first"))) === undefined &&
|
||||
(yield* catalog.provider.get(ProviderV2.ID.make("second"))) !== undefined &&
|
||||
(yield* agents.get(AgentV2.ID.make("configured")))?.description === "Second plugin"
|
||||
(yield* catalog.provider.get(ProviderV2.ID.make("second"))) !== undefined
|
||||
)
|
||||
}),
|
||||
)
|
||||
@@ -81,10 +80,7 @@ describe("config plugin reloads", () => {
|
||||
expect(
|
||||
(yield* skills.sources()).some((source) => source.type === "directory" && source.path === "/skills/second"),
|
||||
).toBe(true)
|
||||
|
||||
entries = [config("second")]
|
||||
yield* events.publish(ConfigSchema.Event.Updated, {})
|
||||
yield* waitUntil(agents.get(AgentV2.ID.make("configured")).pipe(Effect.map((agent) => agent === undefined)))
|
||||
expect((yield* agents.get(AgentV2.ID.make("configured")))?.description).toBe("First plugin")
|
||||
}).pipe(Effect.provideService(Global.Service, Global.Service.of(Global.make()))),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -15,6 +15,8 @@ import eventSourcedSessionInputMigration from "@opencode-ai/core/database/migrat
|
||||
import contextEpochAgentMigration from "@opencode-ai/core/database/migration/20260605042240_add_context_epoch_agent"
|
||||
import simplifyIntegrationCredentialsMigration from "@opencode-ai/core/database/migration/20260611192811_lush_chimera"
|
||||
import simplifySessionInputMigration from "@opencode-ai/core/database/migration/20260622202450_simplify_session_input"
|
||||
import resetSessionEventsMigration from "@opencode-ai/core/database/migration/20260703200000_reset_v2_session_events"
|
||||
import durableSessionInboxMigration from "@opencode-ai/core/database/migration/20260704161518_durable_session_inbox"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
@@ -38,6 +40,29 @@ const run = <A, E>(effect: Effect.Effect<A, E, SqlClientService>) =>
|
||||
const makeDb = EffectDrizzleSqlite.makeWithDefaults()
|
||||
|
||||
describe("DatabaseMigration", () => {
|
||||
test("resets incompatible V2 Session event history", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(sql`CREATE TABLE session_input (id text PRIMARY KEY)`)
|
||||
yield* db.run(sql`CREATE TABLE session_message (id text PRIMARY KEY)`)
|
||||
yield* db.run(sql`CREATE TABLE event (id text PRIMARY KEY)`)
|
||||
yield* db.run(sql`CREATE TABLE event_sequence (aggregate_id text PRIMARY KEY, seq integer NOT NULL)`)
|
||||
yield* db.run(sql`INSERT INTO session_input (id) VALUES ('input')`)
|
||||
yield* db.run(sql`INSERT INTO session_message (id) VALUES ('message')`)
|
||||
yield* db.run(sql`INSERT INTO event (id) VALUES ('event')`)
|
||||
yield* db.run(sql`INSERT INTO event_sequence (aggregate_id, seq) VALUES ('session', 1)`)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [resetSessionEventsMigration])
|
||||
|
||||
expect(yield* db.get(sql`SELECT id FROM session_input`)).toBeUndefined()
|
||||
expect(yield* db.get(sql`SELECT id FROM session_message`)).toBeUndefined()
|
||||
expect(yield* db.get(sql`SELECT id FROM event`)).toBeUndefined()
|
||||
expect(yield* db.get(sql`SELECT aggregate_id FROM event_sequence`)).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("serializes concurrent embedded initialization for one database path", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const filename = path.join(tmp.path, "embedded.sqlite")
|
||||
@@ -83,13 +108,14 @@ describe("DatabaseMigration", () => {
|
||||
expect(yield* db.get(sql`SELECT count(*) as count FROM migration`)).toEqual({ count: migrations.length })
|
||||
expect(
|
||||
yield* db.all(
|
||||
sql`SELECT name FROM sqlite_master WHERE type = 'index' AND name IN ('event_aggregate_seq_idx', 'event_aggregate_type_seq_idx', 'session_input_session_pending_seq_idx', 'session_input_session_pending_delivery_seq_idx', 'session_input_session_admitted_seq_idx', 'session_input_session_promoted_seq_idx', 'session_message_session_idx', 'session_message_session_type_idx', 'session_message_session_seq_idx', 'session_message_session_type_seq_idx', 'session_message_session_time_created_id_idx') ORDER BY name`,
|
||||
sql`SELECT name FROM sqlite_master WHERE type = 'index' AND name IN ('event_aggregate_seq_idx', 'event_aggregate_type_seq_idx', 'session_input_session_pending_seq_idx', 'session_input_session_pending_delivery_seq_idx', 'session_input_session_pending_type_delivery_seq_idx', 'session_input_session_pending_compaction_idx', 'session_input_session_admitted_seq_idx', 'session_input_session_promoted_seq_idx', 'session_message_session_idx', 'session_message_session_type_idx', 'session_message_session_seq_idx', 'session_message_session_type_seq_idx', 'session_message_session_time_created_id_idx') ORDER BY name`,
|
||||
),
|
||||
).toEqual([
|
||||
{ name: "event_aggregate_seq_idx" },
|
||||
{ name: "event_aggregate_type_seq_idx" },
|
||||
{ name: "session_input_session_admitted_seq_idx" },
|
||||
{ name: "session_input_session_pending_delivery_seq_idx" },
|
||||
{ name: "session_input_session_pending_compaction_idx" },
|
||||
{ name: "session_input_session_pending_type_delivery_seq_idx" },
|
||||
{ name: "session_input_session_promoted_seq_idx" },
|
||||
{ name: "session_message_session_seq_idx" },
|
||||
{ name: "session_message_session_time_created_id_idx" },
|
||||
@@ -258,7 +284,7 @@ describe("DatabaseMigration", () => {
|
||||
sql`INSERT INTO event (id, aggregate_id, seq, type, data, created) VALUES ('event', 'session', 9, 'session.updated.1', '{}', 1)`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO session_input (id, session_id, prompt, delivery, admitted_seq, time_created) VALUES ('input', 'session', '{}', 'steer', 9, 1)`,
|
||||
sql`INSERT INTO session_input (id, session_id, type, prompt, delivery, admitted_seq, time_created) VALUES ('input', 'session', 'prompt', '{}', 'steer', 9, 1)`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO session_message (id, session_id, type, seq, time_created, time_updated, data) VALUES ('projected', 'session', 'user', 9, 1, 1, '{}')`,
|
||||
@@ -319,6 +345,37 @@ describe("DatabaseMigration", () => {
|
||||
)
|
||||
})
|
||||
|
||||
test("preserves admitted prompts while generalizing the durable inbox", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
const db = yield* makeDb
|
||||
yield* db.run(
|
||||
sql`CREATE TABLE session_input (id text PRIMARY KEY, session_id text NOT NULL, prompt text NOT NULL, delivery text NOT NULL, admitted_seq integer NOT NULL, promoted_seq integer, time_created integer NOT NULL)`,
|
||||
)
|
||||
yield* db.run(
|
||||
sql`INSERT INTO session_input (id, session_id, prompt, delivery, admitted_seq, promoted_seq, time_created) VALUES ('input', 'session', '{"text":"hello"}', 'steer', 4, NULL, 1)`,
|
||||
)
|
||||
|
||||
yield* DatabaseMigration.applyOnly(db, [durableSessionInboxMigration])
|
||||
|
||||
expect(
|
||||
yield* db.all(
|
||||
sql`SELECT id, type, prompt, delivery, admitted_seq, promoted_seq FROM session_input ORDER BY admitted_seq`,
|
||||
),
|
||||
).toEqual([
|
||||
{
|
||||
id: "input",
|
||||
type: "prompt",
|
||||
prompt: '{"text":"hello"}',
|
||||
delivery: "steer",
|
||||
admitted_seq: 4,
|
||||
promoted_seq: null,
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test("resets incompatible projected Session messages before adding sequence order", async () => {
|
||||
await run(
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { Ignore } from "@opencode-ai/core/filesystem/ignore"
|
||||
// @ts-ignore
|
||||
import { createWrapper } from "@parcel/watcher/wrapper"
|
||||
|
||||
test("match nested and non-nested", () => {
|
||||
expect(Ignore.match("node_modules/index.js")).toBe(true)
|
||||
@@ -8,3 +10,30 @@ test("match nested and non-nested", () => {
|
||||
expect(Ignore.match("node_modules/bar")).toBe(true)
|
||||
expect(Ignore.match("node_modules/bar/")).toBe(true)
|
||||
})
|
||||
|
||||
test("parcel patterns ignore built-in folders at any depth", async () => {
|
||||
let ignoreGlobs: string[] = []
|
||||
const watcher = createWrapper({
|
||||
subscribe: async (
|
||||
_directory: string,
|
||||
_callback: (...args: unknown[]) => unknown,
|
||||
options: { ignoreGlobs?: string[] },
|
||||
) => {
|
||||
ignoreGlobs = options.ignoreGlobs ?? []
|
||||
},
|
||||
})
|
||||
await watcher.subscribe("/tmp/project", () => {}, { ignore: Ignore.PATTERNS })
|
||||
const patterns = ignoreGlobs.map((source) => new RegExp(source))
|
||||
|
||||
for (const path of [
|
||||
"nested/node_modules",
|
||||
"nested/node_modules/package/index.js",
|
||||
"nested/.git",
|
||||
"nested/.git/HEAD",
|
||||
"nested/dist",
|
||||
"nested/dist/index.js",
|
||||
]) {
|
||||
expect(patterns.some((pattern) => pattern.test(path))).toBe(true)
|
||||
}
|
||||
expect(patterns.some((pattern) => pattern.test("nested/src/index.ts"))).toBe(false)
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { $ } from "bun"
|
||||
import { describe, expect } from "bun:test"
|
||||
import fs from "fs/promises"
|
||||
import path from "path"
|
||||
import { Deferred, Duration, Effect, Fiber, Layer, Option, Stream } from "effect"
|
||||
import { Deferred, Duration, Effect, Fiber, Layer, Option, Schedule, Stream } from "effect"
|
||||
import { Config } from "@opencode-ai/core/config"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
@@ -149,12 +149,14 @@ describeWatcher("LocationWatcher", () => {
|
||||
const update = yield* watcher
|
||||
.subscribe({ path: target, type: "file" })
|
||||
.pipe(Stream.take(1), Stream.runHead, Effect.forkScoped({ startImmediately: true }))
|
||||
yield* Effect.yieldNow
|
||||
|
||||
yield* fs.writeFileString(sibling, "sibling")
|
||||
yield* fs.writeFileString(target, "target")
|
||||
const writes = yield* Effect.suspend(() => fs.writeFileString(target, `target-${Math.random()}`)).pipe(
|
||||
Effect.repeat(Schedule.spaced("10 millis")),
|
||||
Effect.forkScoped,
|
||||
)
|
||||
const event = yield* Fiber.join(update).pipe(Effect.ensuring(Fiber.interrupt(writes)))
|
||||
|
||||
expect((yield* Fiber.join(update)).valueOrUndefined?.path).toBe(target)
|
||||
expect(event.valueOrUndefined?.path).toBe(target)
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(Watcher.node))),
|
||||
),
|
||||
)
|
||||
@@ -197,6 +199,24 @@ describeWatcher("LocationWatcher", () => {
|
||||
),
|
||||
)
|
||||
|
||||
it.live("ignores dependency, VCS, and build directories at any depth", () =>
|
||||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const afs = yield* FSUtil.Service
|
||||
yield* ready(directory)
|
||||
const roots = ["node_modules", ".git", "dist"].map((name) => path.join(directory, "nested", name))
|
||||
const files = roots.map((root) => path.join(root, "package", "index.js"))
|
||||
yield* noUpdate(
|
||||
(event) => roots.some((root) => event.file === root || event.file.startsWith(`${root}${path.sep}`)),
|
||||
Effect.forEach(files, (file) => afs.writeWithDirs(file, "ignored"), {
|
||||
concurrency: "unbounded",
|
||||
discard: true,
|
||||
}),
|
||||
)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.live("cleanup stops publishing events", () =>
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
||||
@@ -74,7 +74,7 @@ const it = testEffect(
|
||||
)
|
||||
|
||||
describe("SessionV2.compact", () => {
|
||||
it.effect("manually compacts the active session context", () =>
|
||||
it.effect("durably admits and coalesces manual compaction", () =>
|
||||
Effect.gen(function* () {
|
||||
requests = []
|
||||
const session = yield* SessionV2.Service
|
||||
@@ -94,13 +94,22 @@ describe("SessionV2.compact", () => {
|
||||
inputID: messageID,
|
||||
})
|
||||
|
||||
yield* session.compact({ sessionID: created.id })
|
||||
expect(yield* session.compact({ id: messageID, sessionID: created.id }).pipe(Effect.flip)).toMatchObject({
|
||||
_tag: "Session.CompactionConflictError",
|
||||
inputID: messageID,
|
||||
})
|
||||
const first = yield* session.compact({ sessionID: created.id })
|
||||
const second = yield* session.compact({ sessionID: created.id })
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(JSON.stringify(requests[0]?.messages)).toContain("Please compact this session history.")
|
||||
expect(yield* session.context(created.id)).toMatchObject([
|
||||
{ type: "compaction", reason: "manual", summary: "manual session summary", recent: "" },
|
||||
])
|
||||
expect(second.id).toBe(first.id)
|
||||
expect(requests).toHaveLength(0)
|
||||
expect((yield* session.context(created.id)).find((message) => message.id === first.id)).toMatchObject({
|
||||
type: "compaction",
|
||||
status: "queued",
|
||||
reason: "manual",
|
||||
summary: "",
|
||||
recent: "",
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Project } from "@opencode-ai/core/project"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { DateTime, Effect, Layer, Stream } from "effect"
|
||||
import { DateTime, Effect, Fiber, Layer, Stream } from "effect"
|
||||
import { asc, eq } from "drizzle-orm"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
@@ -68,11 +68,38 @@ test("compaction describes tool media without embedding base64", () => {
|
||||
expect(serialized).not.toContain(base64)
|
||||
})
|
||||
|
||||
test("compaction prompt requires the continuation checkpoint headings in order", () => {
|
||||
const prompt = SessionCompaction.buildPrompt({ context: ["Conversation history"] })
|
||||
expect(prompt.match(/^#{2,3} .+$/gm)).toEqual([
|
||||
"## Continuation Goal",
|
||||
"## Operating Constraints",
|
||||
"## Progress",
|
||||
"### Completed",
|
||||
"### In Flight",
|
||||
"### Blocked",
|
||||
"## Decisions To Preserve",
|
||||
"## Resume From Here",
|
||||
"## Context To Preserve",
|
||||
"## Working Files",
|
||||
])
|
||||
expect(prompt).toContain("single-sentence task summary")
|
||||
expect(prompt).toContain("user constraints, preferences, specs")
|
||||
expect(prompt).toContain("completed work")
|
||||
expect(prompt).toContain("current work")
|
||||
expect(prompt).toContain("blockers")
|
||||
expect(prompt).toContain("decision and why")
|
||||
expect(prompt).toContain("ordered next actions")
|
||||
expect(prompt).toContain("important technical facts, errors, open questions")
|
||||
expect(prompt).toContain("file or directory path: why it matters")
|
||||
expect(prompt).toContain("Keep every section, even when empty.")
|
||||
})
|
||||
|
||||
it.effect("manual compaction summarizes short context instead of no-op", () =>
|
||||
Effect.gen(function* () {
|
||||
requests = []
|
||||
const db = (yield* Database.Service).db
|
||||
const compaction = yield* SessionCompaction.Service
|
||||
const events = yield* EventV2.Service
|
||||
const store = yield* SessionStore.Service
|
||||
const sessionID = SessionV2.ID.make("ses_manual_compaction")
|
||||
const userMessage = {
|
||||
@@ -108,7 +135,12 @@ it.effect("manual compaction summarizes short context instead of no-op", () =>
|
||||
),
|
||||
)
|
||||
|
||||
const delta = yield* events
|
||||
.subscribe(SessionEvent.Compaction.Delta)
|
||||
.pipe(Stream.take(1), Stream.runCollect, Effect.forkScoped)
|
||||
yield* Effect.yieldNow
|
||||
expect(yield* compaction.compactManual({ session, messages: [userMessage] })).toBe(true)
|
||||
expect(Array.from(yield* Fiber.join(delta)).map((event) => event.data.text)).toEqual(["manual summary"])
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(JSON.stringify(requests[0]?.messages)).toContain("Manual compaction should include this short conversation.")
|
||||
|
||||
@@ -562,9 +562,9 @@ describe("SessionV2.create", () => {
|
||||
yield* session.switchModel({ sessionID: created.id, model })
|
||||
|
||||
expect(yield* session.get(created.id)).toMatchObject({ model })
|
||||
expect(
|
||||
Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect)),
|
||||
).toMatchObject([{ type: "session.model.selected", data: { model } }])
|
||||
const events = Array.from(yield* logEvents(session, created.id, true).pipe(Stream.take(1), Stream.runCollect))
|
||||
expect(events).toMatchObject([{ type: "session.model.selected" }])
|
||||
expect(events[0]?.data).toEqual({ sessionID: created.id, model })
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import {
|
||||
AuthenticationReason,
|
||||
ContentPolicyReason,
|
||||
InvalidProviderOutputReason,
|
||||
InvalidRequestReason,
|
||||
LLMError,
|
||||
NoRouteReason,
|
||||
ModelID,
|
||||
ProviderID,
|
||||
ProviderInternalReason,
|
||||
QuotaExceededReason,
|
||||
RateLimitReason,
|
||||
TransportReason,
|
||||
UnknownProviderReason,
|
||||
ToolFailure,
|
||||
} from "@opencode-ai/llm"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { toSessionError } from "@opencode-ai/core/session/to-session-error"
|
||||
import { SessionRunnerRetry } from "@opencode-ai/core/session/runner/retry"
|
||||
|
||||
const llm = (reason: LLMError["reason"]) => new LLMError({ module: "test", method: "stream", reason })
|
||||
|
||||
describe("toSessionError", () => {
|
||||
test("maps every LLM reason to the open wire type", () => {
|
||||
expect(toSessionError(llm(new RateLimitReason({ message: "rate", retryAfterMs: 123 })))).toEqual({
|
||||
type: "provider.rate-limit",
|
||||
message: "rate",
|
||||
})
|
||||
expect(toSessionError(llm(new AuthenticationReason({ message: "auth", kind: "invalid" }))).type).toBe(
|
||||
"provider.auth",
|
||||
)
|
||||
expect(toSessionError(llm(new QuotaExceededReason({ message: "quota" }))).type).toBe("provider.quota")
|
||||
expect(toSessionError(llm(new ContentPolicyReason({ message: "blocked" }))).type).toBe("provider.content-filter")
|
||||
expect(toSessionError(llm(new TransportReason({ message: "transport" }))).type).toBe("provider.transport")
|
||||
expect(toSessionError(llm(new ProviderInternalReason({ message: "internal", status: 500 }))).type).toBe(
|
||||
"provider.internal",
|
||||
)
|
||||
expect(toSessionError(llm(new InvalidProviderOutputReason({ message: "output" }))).type).toBe(
|
||||
"provider.invalid-output",
|
||||
)
|
||||
expect(toSessionError(llm(new InvalidRequestReason({ message: "request" }))).type).toBe("provider.invalid-request")
|
||||
expect(
|
||||
toSessionError(
|
||||
llm(
|
||||
new NoRouteReason({
|
||||
route: "route",
|
||||
provider: ProviderID.make("provider"),
|
||||
model: ModelID.make("model"),
|
||||
}),
|
||||
),
|
||||
).type,
|
||||
).toBe("provider.no-route")
|
||||
expect(toSessionError(llm(new UnknownProviderReason({ message: "unknown" }))).type).toBe("provider.unknown")
|
||||
})
|
||||
|
||||
test("preserves the permission rejection type without exposing internal fields", () => {
|
||||
const rejected = new PermissionV2.RejectedError({ permission: "external_directory", resources: [] })
|
||||
expect(toSessionError(rejected)).toEqual({
|
||||
type: "permission.rejected",
|
||||
message: "Permission rejected: external_directory",
|
||||
})
|
||||
expect(toSessionError(new ToolFailure({ message: rejected.message, error: rejected }))).toEqual({
|
||||
type: "permission.rejected",
|
||||
message: "Permission rejected: external_directory",
|
||||
})
|
||||
})
|
||||
|
||||
test("retries only rate limits, provider-internal failures, and transport failures", () => {
|
||||
const eligible = [
|
||||
llm(new RateLimitReason({ message: "rate" })),
|
||||
llm(new ProviderInternalReason({ message: "internal", status: 500 })),
|
||||
llm(new TransportReason({ message: "transport" })),
|
||||
]
|
||||
const ineligible = [
|
||||
llm(new AuthenticationReason({ message: "auth", kind: "invalid" })),
|
||||
llm(new QuotaExceededReason({ message: "quota" })),
|
||||
llm(new ContentPolicyReason({ message: "blocked" })),
|
||||
llm(new InvalidProviderOutputReason({ message: "output" })),
|
||||
llm(new InvalidRequestReason({ message: "request" })),
|
||||
llm(new NoRouteReason({ route: "route", provider: ProviderID.make("provider"), model: ModelID.make("model") })),
|
||||
llm(new UnknownProviderReason({ message: "unknown" })),
|
||||
]
|
||||
|
||||
expect(eligible.map(SessionRunnerRetry.isRetryable)).toEqual([true, true, true])
|
||||
expect(ineligible.map(SessionRunnerRetry.isRetryable)).toEqual([false, false, false, false, false, false, false])
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { LLMError, TransportReason } from "@opencode-ai/llm"
|
||||
import { terminal } from "@opencode-ai/core/session/execution/local"
|
||||
import { UserInterruptedError } from "@opencode-ai/core/session/error"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { Effect, Exit } from "effect"
|
||||
|
||||
describe("SessionExecutionLocal lifecycle", () => {
|
||||
test("classifies success and typed failure terminals", () => {
|
||||
expect(terminal(Exit.succeed(undefined))).toEqual({ type: "succeeded" })
|
||||
expect(
|
||||
terminal(
|
||||
Exit.fail(
|
||||
new LLMError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new TransportReason({ message: "Disconnected" }),
|
||||
}),
|
||||
),
|
||||
),
|
||||
).toEqual({ type: "failed", error: { type: "provider.transport", message: "Disconnected" } })
|
||||
const storage = new ToolOutputStore.StorageError({ operation: "encode", cause: new Error("invalid output") })
|
||||
expect(terminal(Exit.fail(storage))).toEqual({
|
||||
type: "failed",
|
||||
error: { type: "unknown", message: storage.message },
|
||||
})
|
||||
})
|
||||
|
||||
test("defaults owner-scope interruption to shutdown and preserves explicit reasons", () => {
|
||||
const interrupted = Effect.runSyncExit(Effect.interrupt)
|
||||
expect(terminal(interrupted)).toEqual({ type: "interrupted", reason: "shutdown" })
|
||||
expect(terminal(interrupted, "user")).toEqual({ type: "interrupted", reason: "user" })
|
||||
expect(terminal(interrupted, "superseded")).toEqual({ type: "interrupted", reason: "superseded" })
|
||||
expect(terminal(Exit.fail(new UserInterruptedError()))).toEqual({ type: "interrupted", reason: "user" })
|
||||
})
|
||||
})
|
||||
@@ -34,6 +34,7 @@ const sessionsLayer = AppNodeBuilder.build(SessionV2.node, [[SessionExecution.no
|
||||
const sessionID = SessionV2.ID.make("ses_projector_test")
|
||||
const created = DateTime.makeUnsafe(0)
|
||||
const model = { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") }
|
||||
const previousModel = { ...model, variant: ModelV2.VariantID.make("medium") }
|
||||
const encodeMessage = Schema.encodeSync(SessionMessage.Message)
|
||||
|
||||
const assistantRow = (
|
||||
@@ -95,7 +96,7 @@ describe("SessionProjector", () => {
|
||||
})
|
||||
yield* events.publish(SessionEvent.RevertEvent.Committed, {
|
||||
sessionID,
|
||||
messageID: boundary,
|
||||
to: boundary,
|
||||
})
|
||||
expect(
|
||||
(yield* db.select({ id: SessionMessageTable.id }).from(SessionMessageTable).all()).map((row) => row.id),
|
||||
@@ -238,6 +239,7 @@ describe("SessionProjector", () => {
|
||||
directory: "/project",
|
||||
title: "test",
|
||||
version: "test",
|
||||
model: previousModel,
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
@@ -337,6 +339,7 @@ describe("SessionProjector", () => {
|
||||
text: "synthetic context",
|
||||
metadata: { source: "projector-test" },
|
||||
})
|
||||
expect(messages.find((message) => message.type === "model-switched")).toMatchObject({ previous: previousModel })
|
||||
expect(messages.find((message) => message.type === "shell")).toMatchObject({
|
||||
shell: { command: "pwd", status: "exited", exit: 0 },
|
||||
output: { output: "/project", truncated: false },
|
||||
@@ -429,6 +432,73 @@ describe("SessionProjector", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("projects retry state and clears it at the next step or execution terminal", () =>
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
yield* db
|
||||
.insert(ProjectTable)
|
||||
.values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] })
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.insert(SessionTable)
|
||||
.values({
|
||||
id: sessionID,
|
||||
project_id: Project.ID.global,
|
||||
slug: "test",
|
||||
directory: "/project",
|
||||
title: "test",
|
||||
version: "test",
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
const events = yield* EventV2.Service
|
||||
const first = SessionMessage.ID.make("msg_retry_first")
|
||||
const second = SessionMessage.ID.make("msg_retry_second")
|
||||
yield* events.publish(SessionEvent.Step.Started, { sessionID, assistantMessageID: first, agent: "build", model })
|
||||
yield* events.publish(SessionEvent.RetryScheduled, {
|
||||
sessionID,
|
||||
assistantMessageID: first,
|
||||
attempt: 2,
|
||||
at: 2_000,
|
||||
error: { type: "provider.transport", message: "Disconnected" },
|
||||
})
|
||||
|
||||
const decode = (row: typeof SessionMessageTable.$inferSelect) =>
|
||||
Schema.decodeUnknownSync(SessionMessage.Message)({ ...row.data, id: row.id, type: row.type })
|
||||
const firstRow = yield* db
|
||||
.select()
|
||||
.from(SessionMessageTable)
|
||||
.where(eq(SessionMessageTable.id, first))
|
||||
.get()
|
||||
.pipe(Effect.orDie)
|
||||
const projected = firstRow ?? (yield* Effect.die(new Error("Missing retry projection")))
|
||||
expect(decode(projected)).toMatchObject({
|
||||
retry: { attempt: 2, at: DateTime.makeUnsafe(2_000), error: { type: "provider.transport" } },
|
||||
})
|
||||
|
||||
yield* events.publish(SessionEvent.Step.Started, { sessionID, assistantMessageID: second, agent: "build", model })
|
||||
yield* events.publish(SessionEvent.RetryScheduled, {
|
||||
sessionID,
|
||||
assistantMessageID: second,
|
||||
attempt: 3,
|
||||
at: 6_000,
|
||||
error: { type: "provider.internal", message: "Unavailable" },
|
||||
})
|
||||
yield* events.publish(SessionEvent.Execution.Interrupted, { sessionID, reason: "shutdown" })
|
||||
|
||||
const rows = yield* db
|
||||
.select()
|
||||
.from(SessionMessageTable)
|
||||
.where(eq(SessionMessageTable.session_id, sessionID))
|
||||
.orderBy(asc(SessionMessageTable.seq))
|
||||
.all()
|
||||
.pipe(Effect.orDie)
|
||||
expect(decode(rows[0])).not.toHaveProperty("retry")
|
||||
expect(decode(rows[1])).not.toHaveProperty("retry")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("updates only the newest incomplete assistant projection", () =>
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
@@ -522,7 +592,7 @@ describe("SessionProjector", () => {
|
||||
yield* service.publish(SessionEvent.Text.Started, {
|
||||
sessionID,
|
||||
assistantMessageID: SessionMessage.ID.make("msg_assistant_completed"),
|
||||
textID: "text-stale",
|
||||
ordinal: 0,
|
||||
})
|
||||
|
||||
const rows = yield* db
|
||||
@@ -541,7 +611,7 @@ describe("SessionProjector", () => {
|
||||
type: "assistant",
|
||||
agent: "build",
|
||||
model,
|
||||
content: [SessionMessage.AssistantText.make({ type: "text", id: "text-stale", text: "" })],
|
||||
content: [SessionMessage.AssistantText.make({ type: "text", text: "" })],
|
||||
time: { created: DateTime.makeUnsafe(1), completed: DateTime.makeUnsafe(2) },
|
||||
}),
|
||||
SessionMessage.Assistant.make({
|
||||
|
||||
@@ -233,7 +233,9 @@ describe("SessionV2.prompt", () => {
|
||||
expect(message.prompt.files).toEqual([
|
||||
{ uri: "data:image/png;base64,aGVsbG8=", name: "image.png", mime: "image/png" },
|
||||
])
|
||||
expect((yield* admitted(message.id))?.prompt.files).toEqual(message.prompt.files)
|
||||
const stored = yield* admitted(message.id)
|
||||
expect(stored?.type).toBe("prompt")
|
||||
if (stored?.type === "prompt") expect(stored.prompt.files).toEqual(message.prompt.files)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -104,8 +104,10 @@ describe("SessionRunCoordinator", () => {
|
||||
Effect.gen(function* () {
|
||||
const failure = new Error("failed")
|
||||
const defect = new Error("defect")
|
||||
const settled: Exit.Exit<void, Error>[] = []
|
||||
const coordinator = yield* SessionRunCoordinator.make({
|
||||
drain: (key: string) => (key === "failure" ? Effect.fail(failure) : Effect.die(defect)),
|
||||
settled: (_key, exit) => Effect.sync(() => void settled.push(exit)),
|
||||
})
|
||||
|
||||
const failed = yield* coordinator.run("failure").pipe(Effect.exit)
|
||||
@@ -115,6 +117,25 @@ describe("SessionRunCoordinator", () => {
|
||||
const died = yield* coordinator.run("defect").pipe(Effect.exit)
|
||||
expect(Exit.isFailure(died) && Cause.hasDies(died.cause)).toBeTrue()
|
||||
expect(Array.from(yield* coordinator.active)).toEqual([])
|
||||
expect(settled).toHaveLength(2)
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("preserves settlement hook defects while releasing ownership", () =>
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const defect = new Error("terminal publication failed")
|
||||
const coordinator = yield* SessionRunCoordinator.make({
|
||||
drain: () => Effect.void,
|
||||
settled: () => Effect.die(defect),
|
||||
})
|
||||
|
||||
const exit = yield* coordinator.run("session").pipe(Effect.exit)
|
||||
|
||||
expect(Exit.isFailure(exit) && Cause.hasDies(exit.cause)).toBe(true)
|
||||
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBe(defect)
|
||||
expect(yield* coordinator.active).toEqual(new Set())
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -209,8 +230,41 @@ describe("SessionRunCoordinator", () => {
|
||||
it.effect("does nothing when interrupted while idle", () =>
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const coordinator = yield* SessionRunCoordinator.make({ drain: () => Effect.void })
|
||||
yield* coordinator.interrupt("session")
|
||||
const reasons: Array<string | undefined> = []
|
||||
const coordinator = yield* SessionRunCoordinator.make<string, never, string>({
|
||||
drain: () => Effect.void,
|
||||
settled: (_key, _exit, reason) => Effect.sync(() => void reasons.push(reason)),
|
||||
})
|
||||
yield* coordinator.interrupt("session", "user")
|
||||
yield* coordinator.run("session")
|
||||
expect(reasons).toEqual([undefined])
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("does not attach a late interrupt reason after terminal settlement starts", () =>
|
||||
Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const settling = yield* Deferred.make<void>()
|
||||
const release = yield* Deferred.make<void>()
|
||||
const reasons: Array<string | undefined> = []
|
||||
const coordinator = yield* SessionRunCoordinator.make<string, never, string>({
|
||||
drain: () => Effect.void,
|
||||
settled: (_key, _exit, reason) =>
|
||||
Deferred.succeed(settling, undefined).pipe(
|
||||
Effect.andThen(Deferred.await(release)),
|
||||
Effect.andThen(Effect.sync(() => void reasons.push(reason))),
|
||||
),
|
||||
})
|
||||
|
||||
const run = yield* coordinator.run("session").pipe(Effect.forkChild)
|
||||
yield* Deferred.await(settling)
|
||||
yield* coordinator.interrupt("session", "user")
|
||||
yield* Deferred.succeed(release, undefined)
|
||||
yield* Fiber.join(run)
|
||||
yield* coordinator.run("session")
|
||||
|
||||
expect(reasons).toEqual([undefined, undefined])
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -221,25 +275,28 @@ describe("SessionRunCoordinator", () => {
|
||||
const started = yield* Deferred.make<void>()
|
||||
const interrupted = yield* Deferred.make<void>()
|
||||
let runs = 0
|
||||
const coordinator = yield* SessionRunCoordinator.make({
|
||||
const reasons: Array<string | undefined> = []
|
||||
const coordinator = yield* SessionRunCoordinator.make<string, never, string>({
|
||||
drain: () =>
|
||||
Effect.sync(() => ++runs).pipe(
|
||||
Effect.andThen(Deferred.succeed(started, undefined)),
|
||||
Effect.andThen(Effect.never),
|
||||
Effect.onInterrupt(() => Deferred.succeed(interrupted, undefined)),
|
||||
),
|
||||
settled: (_key, _exit, reason) => Effect.sync(() => void reasons.push(reason)),
|
||||
})
|
||||
|
||||
const resumed = yield* coordinator.run("session").pipe(Effect.forkChild)
|
||||
yield* Deferred.await(started)
|
||||
yield* coordinator.wake("session")
|
||||
yield* coordinator.interrupt("session")
|
||||
yield* coordinator.interrupt("session", "user")
|
||||
yield* Deferred.await(interrupted)
|
||||
|
||||
const exit = yield* Fiber.await(resumed)
|
||||
expect(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)).toBeTrue()
|
||||
expect(Array.from(yield* coordinator.active)).toEqual([])
|
||||
expect(runs).toBe(1)
|
||||
expect(reasons).toEqual(["user"])
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -252,6 +309,7 @@ describe("SessionRunCoordinator", () => {
|
||||
const cleanupGate = yield* Deferred.make<void>()
|
||||
const secondStarted = yield* Deferred.make<void>()
|
||||
let runs = 0
|
||||
let starts = 0
|
||||
const coordinator = yield* SessionRunCoordinator.make({
|
||||
drain: () =>
|
||||
Effect.sync(() => ++runs).pipe(
|
||||
@@ -266,6 +324,7 @@ describe("SessionRunCoordinator", () => {
|
||||
: Deferred.succeed(secondStarted, undefined),
|
||||
),
|
||||
),
|
||||
started: () => Effect.sync(() => starts++).pipe(Effect.asVoid),
|
||||
})
|
||||
|
||||
yield* coordinator.wake("session")
|
||||
@@ -278,6 +337,7 @@ describe("SessionRunCoordinator", () => {
|
||||
yield* Deferred.await(secondStarted)
|
||||
|
||||
expect(runs).toBe(2)
|
||||
expect(starts).toBe(2)
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -399,6 +459,7 @@ describe("SessionRunCoordinator", () => {
|
||||
const gate = yield* Deferred.make<void>()
|
||||
const idle = yield* Deferred.make<void>()
|
||||
let drains = 0
|
||||
let starts = 0
|
||||
const settled: Exit.Exit<void, never>[] = []
|
||||
const coordinator = yield* SessionRunCoordinator.make<string, never>({
|
||||
drain: () =>
|
||||
@@ -410,6 +471,7 @@ describe("SessionRunCoordinator", () => {
|
||||
),
|
||||
Effect.asVoid,
|
||||
),
|
||||
started: () => Effect.sync(() => starts++).pipe(Effect.asVoid),
|
||||
settled: (_key, exit) =>
|
||||
Effect.sync(() => void settled.push(exit)).pipe(
|
||||
Effect.andThen(Deferred.succeed(idle, undefined)),
|
||||
@@ -424,6 +486,7 @@ describe("SessionRunCoordinator", () => {
|
||||
yield* Deferred.await(idle)
|
||||
|
||||
expect(drains).toBe(2)
|
||||
expect(starts).toBe(1)
|
||||
expect(settled).toHaveLength(1)
|
||||
expect(Exit.isSuccess(settled[0]!)).toBe(true)
|
||||
}),
|
||||
|
||||
@@ -28,17 +28,14 @@ describe("toLLMMessages", () => {
|
||||
const messages = toLLMMessages(
|
||||
[
|
||||
assistant("empty", []),
|
||||
assistant("empty-text", [SessionMessage.AssistantText.make({ type: "text", id: "empty", text: "" })]),
|
||||
assistant("empty-reasoning", [
|
||||
SessionMessage.AssistantReasoning.make({ type: "reasoning", id: "empty-reasoning", text: "" }),
|
||||
]),
|
||||
assistant("text", [SessionMessage.AssistantText.make({ type: "text", id: "text", text: "Partial" })]),
|
||||
assistant("empty-text", [SessionMessage.AssistantText.make({ type: "text", text: "" })]),
|
||||
assistant("empty-reasoning", [SessionMessage.AssistantReasoning.make({ type: "reasoning", text: "" })]),
|
||||
assistant("text", [SessionMessage.AssistantText.make({ type: "text", text: "Partial" })]),
|
||||
assistant("reasoning", [
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
id: "reasoning",
|
||||
text: "",
|
||||
providerMetadata: { anthropic: { signature: "sig_1" } },
|
||||
state: { signature: "sig_1" },
|
||||
}),
|
||||
]),
|
||||
],
|
||||
@@ -104,8 +101,9 @@ describe("toLLMMessages", () => {
|
||||
}),
|
||||
SessionMessage.Compaction.make({
|
||||
id: id("compaction"),
|
||||
type: "compaction",
|
||||
reason: "auto",
|
||||
type: "compaction",
|
||||
status: "completed",
|
||||
reason: "auto",
|
||||
summary: "Earlier work",
|
||||
recent: "Recent work",
|
||||
time: { created },
|
||||
@@ -158,12 +156,11 @@ Recent work
|
||||
agent: "build",
|
||||
model: { id: ModelV2.ID.make("model"), providerID: ProviderV2.ID.make("provider") },
|
||||
content: [
|
||||
SessionMessage.AssistantText.make({ type: "text", id: "text-1", text: "Checking" }),
|
||||
SessionMessage.AssistantText.make({ type: "text", text: "Checking" }),
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
id: "reasoning-1",
|
||||
text: "Think",
|
||||
providerMetadata: { anthropic: { signature: "sig_1" } },
|
||||
state: { signature: "sig_1" },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
@@ -208,11 +205,9 @@ Recent work
|
||||
type: "tool",
|
||||
id: "hosted",
|
||||
name: "web_search",
|
||||
provider: {
|
||||
executed: true,
|
||||
metadata: { fake: { continuation: "hosted-call" } },
|
||||
resultMetadata: { fake: { continuation: "hosted-result" } },
|
||||
},
|
||||
executed: true,
|
||||
providerState: { continuation: "hosted-call" },
|
||||
providerResultState: { continuation: "hosted-result" },
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
input: { query: "Effect" },
|
||||
@@ -225,7 +220,8 @@ Recent work
|
||||
type: "tool",
|
||||
id: "hosted-failed",
|
||||
name: "write",
|
||||
provider: { executed: true, metadata: { fake: { continuation: "failed" } } },
|
||||
executed: true,
|
||||
providerState: { continuation: "failed" },
|
||||
state: SessionMessage.ToolStateError.make({
|
||||
status: "error",
|
||||
input: { path: "README.md" },
|
||||
@@ -245,7 +241,7 @@ Recent work
|
||||
expect(messages.map((message) => message.role)).toEqual(["assistant", "tool"])
|
||||
expect(messages[0]?.content).toEqual([
|
||||
{ type: "text", text: "Checking" },
|
||||
{ type: "reasoning", text: "Think", providerMetadata: { anthropic: { signature: "sig_1" } } },
|
||||
{ type: "reasoning", text: "Think", providerMetadata: { provider: { signature: "sig_1" } } },
|
||||
{ type: "tool-call", id: "pending", name: "read", input: { path: "README.md" } },
|
||||
{ type: "tool-call", id: "running", name: "read", input: { path: "README.md" } },
|
||||
{
|
||||
@@ -260,14 +256,14 @@ Recent work
|
||||
name: "web_search",
|
||||
input: { query: "Effect" },
|
||||
providerExecuted: true,
|
||||
providerMetadata: { fake: { continuation: "hosted-call" } },
|
||||
providerMetadata: { provider: { continuation: "hosted-call" } },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
id: "hosted",
|
||||
name: "web_search",
|
||||
providerExecuted: true,
|
||||
providerMetadata: { fake: { continuation: "hosted-result" } },
|
||||
providerMetadata: { provider: { continuation: "hosted-result" } },
|
||||
result: { type: "text", value: "Found it" },
|
||||
},
|
||||
{
|
||||
@@ -276,14 +272,14 @@ Recent work
|
||||
name: "write",
|
||||
input: { path: "README.md" },
|
||||
providerExecuted: true,
|
||||
providerMetadata: { fake: { continuation: "failed" } },
|
||||
providerMetadata: { provider: { continuation: "failed" } },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
id: "hosted-failed",
|
||||
name: "write",
|
||||
providerExecuted: true,
|
||||
providerMetadata: { fake: { continuation: "failed" } },
|
||||
providerMetadata: { provider: { continuation: "failed" } },
|
||||
result: {
|
||||
type: "error",
|
||||
value: { error: { type: "unknown", message: "Denied" }, content: [], structured: {} },
|
||||
@@ -317,9 +313,8 @@ Recent work
|
||||
content: [
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
id: "reasoning-openai",
|
||||
text: "Think",
|
||||
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
}),
|
||||
],
|
||||
time: { created, completed: created },
|
||||
@@ -332,7 +327,7 @@ Recent work
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Think",
|
||||
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
providerMetadata: { provider: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
},
|
||||
])
|
||||
})
|
||||
@@ -348,19 +343,16 @@ Recent work
|
||||
content: [
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
id: "reasoning-failed",
|
||||
text: "Partial thought",
|
||||
providerMetadata: { openai: { itemId: "rs_failed", reasoningEncryptedContent: null } },
|
||||
state: { itemId: "rs_failed", reasoningEncryptedContent: null },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "hosted-failed",
|
||||
name: "web_search",
|
||||
provider: {
|
||||
executed: true,
|
||||
metadata: { openai: { itemId: "call_failed" } },
|
||||
resultMetadata: { openai: { itemId: "result_failed" } },
|
||||
},
|
||||
executed: true,
|
||||
providerState: { itemId: "call_failed" },
|
||||
providerResultState: { itemId: "result_failed" },
|
||||
state: SessionMessage.ToolStateError.make({
|
||||
status: "error",
|
||||
input: { query: "Effect" },
|
||||
@@ -420,19 +412,16 @@ Recent work
|
||||
content: [
|
||||
SessionMessage.AssistantReasoning.make({
|
||||
type: "reasoning",
|
||||
id: "reasoning-old-model",
|
||||
text: "Visible thought",
|
||||
providerMetadata: { anthropic: { signature: "sig_old" } },
|
||||
state: { signature: "sig_old" },
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "hosted-old-model",
|
||||
name: "web_search",
|
||||
provider: {
|
||||
executed: true,
|
||||
metadata: { openai: { itemId: "hosted-old-model" } },
|
||||
resultMetadata: { openai: { itemId: "hosted-old-model" } },
|
||||
},
|
||||
executed: true,
|
||||
providerState: { itemId: "hosted-old-model" },
|
||||
providerResultState: { itemId: "hosted-old-model" },
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
input: { query: "Effect" },
|
||||
@@ -446,11 +435,9 @@ Recent work
|
||||
type: "tool",
|
||||
id: "local-old-model",
|
||||
name: "read",
|
||||
provider: {
|
||||
executed: false,
|
||||
metadata: { fake: { call: "old" } },
|
||||
resultMetadata: { fake: { result: "old" } },
|
||||
},
|
||||
executed: false,
|
||||
providerState: { call: "old" },
|
||||
providerResultState: { result: "old" },
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
input: { path: "README.md" },
|
||||
|
||||
@@ -47,6 +47,7 @@ const capture = () => {
|
||||
id: ModelV2.ID.make("model"),
|
||||
providerID: ProviderV2.ID.make("provider"),
|
||||
},
|
||||
provider: "openai",
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -90,7 +91,7 @@ test("local tool success serializes media base64 once and reconstructs from stru
|
||||
})
|
||||
})
|
||||
|
||||
test("provider-executed success retains its compatibility result", async () => {
|
||||
test("provider-executed success retains its raw provider result", async () => {
|
||||
const { published, publisher } = capture()
|
||||
await Effect.runPromise(publisher.publish(LLMEvent.toolCall({ ...call, providerExecuted: true })))
|
||||
await Effect.runPromise(publisher.publish(LLMEvent.toolResult({ ...result, providerExecuted: true })))
|
||||
@@ -98,6 +99,19 @@ test("provider-executed success retains its compatibility result", async () => {
|
||||
expect(success?.data).toHaveProperty("result")
|
||||
})
|
||||
|
||||
test("provider state uses the route provider instead of the catalog provider", async () => {
|
||||
const { published, publisher } = capture()
|
||||
await Effect.runPromise(
|
||||
publisher.publish(
|
||||
LLMEvent.reasoningStart({ id: "reasoning", providerMetadata: { openai: { itemId: "reasoning" } } }),
|
||||
),
|
||||
)
|
||||
|
||||
expect(published.find((event) => event.type === "session.reasoning.started.1")?.data).toMatchObject({
|
||||
state: { itemId: "reasoning" },
|
||||
})
|
||||
})
|
||||
|
||||
test("binary failure emits no success event", async () => {
|
||||
const { published, publisher } = capture()
|
||||
await Effect.runPromise(publisher.publish(call))
|
||||
@@ -114,7 +128,7 @@ test("binary failure emits no success event", async () => {
|
||||
expect(published.some((event) => event.type === "session.tool.failed.1")).toBe(true)
|
||||
})
|
||||
|
||||
test("old success event data containing result still decodes", () => {
|
||||
test("success event data can carry a provider-executed result", () => {
|
||||
const decoded = Schema.decodeUnknownSync(SessionEvent.Tool.Success.data)({
|
||||
sessionID,
|
||||
assistantMessageID: SessionMessage.ID.create(),
|
||||
@@ -122,7 +136,7 @@ test("old success event data containing result still decodes", () => {
|
||||
structured: { type: "media", mime: "image/png" },
|
||||
content: [{ type: "file", uri: `data:image/png;base64,${base64}`, mime: "image/png" }],
|
||||
result: { type: "content", value: [{ type: "file", uri: `data:image/png;base64,${base64}`, mime: "image/png" }] },
|
||||
provider: { executed: false },
|
||||
executed: true,
|
||||
})
|
||||
expect(decoded.result).toMatchObject({ type: "content" })
|
||||
})
|
||||
@@ -135,3 +149,40 @@ test("step finish records settlement without publishing step ended", async () =>
|
||||
expect(published.some((event) => event.type === "step.ended.2")).toBe(false)
|
||||
expect(publisher.stepSettlement()).toMatchObject({ finish: "stop" })
|
||||
})
|
||||
|
||||
test("content-filter finish retains failure evidence until step closeout", async () => {
|
||||
const { published, publisher } = capture()
|
||||
await Effect.runPromise(publisher.publish(LLMEvent.stepStart({ index: 0 })))
|
||||
await Effect.runPromise(publisher.publish(LLMEvent.stepFinish({ index: 0, reason: "content-filter" })))
|
||||
|
||||
expect(published.map((event) => event.type)).toEqual(["session.step.started.1"])
|
||||
await Effect.runPromise(publisher.publishStepFailure())
|
||||
expect(published.map((event) => event.type)).toEqual(["session.step.started.1", "session.step.failed.1"])
|
||||
expect(published.at(-1)?.data).toMatchObject({
|
||||
error: { type: "provider.content-filter", message: "Provider blocked the response" },
|
||||
})
|
||||
expect(publisher.stepSettlement()).toBeUndefined()
|
||||
})
|
||||
|
||||
test("content-filter finish preserves partial streamed text and never ends the step successfully", async () => {
|
||||
const { published, publisher } = capture()
|
||||
await Effect.runPromise(
|
||||
Effect.forEach(
|
||||
[
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.textStart({ id: "text" }),
|
||||
LLMEvent.textDelta({ id: "text", text: "Partial" }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "content-filter" }),
|
||||
],
|
||||
(event) => publisher.publish(event),
|
||||
{ discard: true },
|
||||
),
|
||||
)
|
||||
await Effect.runPromise(publisher.publishStepFailure())
|
||||
|
||||
expect(published.some((event) => event.type === "session.step.ended.1")).toBe(false)
|
||||
expect(published.find((event) => event.type === "session.text.ended.1")?.data).toMatchObject({ text: "Partial" })
|
||||
expect(published.find((event) => event.type === "session.step.failed.1")?.data).toMatchObject({
|
||||
error: { type: "provider.content-filter" },
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
LLMError,
|
||||
LLMEvent,
|
||||
Model,
|
||||
ToolFailure,
|
||||
TransportReason,
|
||||
InvalidRequestReason,
|
||||
RateLimitReason,
|
||||
type LLMClientShape,
|
||||
type LLMRequest,
|
||||
} from "@opencode-ai/llm"
|
||||
@@ -26,6 +28,7 @@ import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { Snapshot } from "@opencode-ai/core/snapshot"
|
||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { UserInterruptedError } from "@opencode-ai/core/session/error"
|
||||
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||
import { SessionTitle } from "@opencode-ai/core/session/title"
|
||||
import { SessionInput } from "@opencode-ai/core/session/input"
|
||||
@@ -61,7 +64,8 @@ import { McpGuidance } from "@opencode-ai/core/mcp/guidance"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Schema, Stream } from "effect"
|
||||
import { Cause, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, Schema, Stream } from "effect"
|
||||
import { TestClock } from "effect/testing"
|
||||
import { asc, eq } from "drizzle-orm"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
@@ -153,7 +157,10 @@ const echo = Layer.effectDiscard(
|
||||
description: "Fail unexpectedly",
|
||||
input: Schema.Struct({}),
|
||||
output: Schema.Struct({}),
|
||||
execute: () => Effect.die("unexpected tool defect"),
|
||||
execute: () =>
|
||||
(toolExecutionGate ? Deferred.await(toolExecutionGate) : Effect.void).pipe(
|
||||
Effect.andThen(Effect.die("unexpected tool defect")),
|
||||
),
|
||||
}),
|
||||
// BigInt output with no model content forces ToolOutputStore.bound onto its
|
||||
// JSON.stringify encode path, which fails with a typed StorageError.
|
||||
@@ -368,6 +375,20 @@ const providerUnavailable = () =>
|
||||
reason: new TransportReason({ message: "Provider unavailable" }),
|
||||
})
|
||||
|
||||
const invalidRequest = () =>
|
||||
new LLMError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new InvalidRequestReason({ message: "Invalid request" }),
|
||||
})
|
||||
|
||||
const rateLimited = (retryAfterMs?: number) =>
|
||||
new LLMError({
|
||||
module: "test",
|
||||
method: "stream",
|
||||
reason: new RateLimitReason({ message: "Rate limited", retryAfterMs }),
|
||||
})
|
||||
|
||||
const setupOverflowRecovery = Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
@@ -405,6 +426,34 @@ const recordedEventTypes = (id: SessionV2.ID) =>
|
||||
)
|
||||
})
|
||||
|
||||
const recordedStepSettlementEvents = (id: SessionV2.ID, assistantMessageID: SessionMessage.ID) =>
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
const settlementTypes = new Set([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.success.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.ended.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
return (yield* db
|
||||
.select({ type: EventTable.type, data: EventTable.data })
|
||||
.from(EventTable)
|
||||
.where(eq(EventTable.aggregate_id, id))
|
||||
.orderBy(asc(EventTable.seq))
|
||||
.all()
|
||||
.pipe(Effect.orDie)).filter(
|
||||
(event) => settlementTypes.has(event.type) && event.data.assistantMessageID === assistantMessageID,
|
||||
)
|
||||
})
|
||||
|
||||
const requireAssistant = (messages: readonly SessionMessage.Message[]) => {
|
||||
const assistant = messages.find((message) => message.type === "assistant")
|
||||
if (!assistant) throw new Error("Assistant message missing")
|
||||
return assistant
|
||||
}
|
||||
|
||||
const replaySessionProjection = (id: SessionV2.ID) =>
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
@@ -455,7 +504,7 @@ const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string
|
||||
LLMEvent.textStart({ id }),
|
||||
...chunks.map((text) => LLMEvent.textDelta({ id, text })),
|
||||
]
|
||||
const expectedContent = { type: "text", id, text }
|
||||
const expectedContent = { type: "text", text }
|
||||
return {
|
||||
delta: SessionEvent.Text.Delta,
|
||||
partialEvents,
|
||||
@@ -475,7 +524,7 @@ const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string
|
||||
LLMEvent.reasoningStart({ id }),
|
||||
...chunks.map((text) => LLMEvent.reasoningDelta({ id, text })),
|
||||
]
|
||||
const expectedContent = { type: "reasoning", id, text }
|
||||
const expectedContent = { type: "reasoning", text }
|
||||
return {
|
||||
delta: SessionEvent.Reasoning.Delta,
|
||||
partialEvents,
|
||||
@@ -510,6 +559,7 @@ const fragmentFixture = (kind: FragmentKind, id: string, chunks: readonly string
|
||||
const verifyEphemeralDeltas = (kind: FragmentKind) =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
requests.length = 0
|
||||
const session = yield* SessionV2.Service
|
||||
const prompt = `Stream ${kind}`
|
||||
const chunks = Array.from({ length: 32 }, (_, index) => `${index},`)
|
||||
@@ -542,6 +592,7 @@ const verifyEphemeralDeltas = (kind: FragmentKind) =>
|
||||
const verifyPartialFlushOnFailure = (kind: FragmentKind) =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
requests.length = 0
|
||||
const session = yield* SessionV2.Service
|
||||
const prompt = `Fail after ${kind}`
|
||||
const fixture = fragmentFixture(kind, fragmentID(kind, "partial"), ["Partial"])
|
||||
@@ -555,10 +606,11 @@ const verifyPartialFlushOnFailure = (kind: FragmentKind) =>
|
||||
{
|
||||
type: "assistant",
|
||||
finish: "error",
|
||||
error: { type: "unknown", message: "Provider unavailable" },
|
||||
error: { type: "provider.transport", message: "Provider unavailable" },
|
||||
content: [fixture.expectedContent],
|
||||
},
|
||||
])
|
||||
expect(requests).toHaveLength(1)
|
||||
})
|
||||
|
||||
const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
|
||||
@@ -583,7 +635,7 @@ const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
|
||||
{
|
||||
type: "assistant",
|
||||
finish: "error",
|
||||
error: { type: "unknown", message: "Step interrupted" },
|
||||
error: { type: "aborted", message: "Step interrupted" },
|
||||
content: [
|
||||
kind === "tool input"
|
||||
? { type: "tool", id: fragmentID(kind, "interrupted"), state: { status: "error" } }
|
||||
@@ -1264,6 +1316,97 @@ describe("SessionRunnerLLM", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("runs one durable compaction barrier before later steer and queued prompts", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
requests.length = 0
|
||||
currentModel = recoveryModel
|
||||
const session = yield* SessionV2.Service
|
||||
streamGate = yield* Deferred.make<void>()
|
||||
streamStarted = yield* Deferred.make<void>()
|
||||
responses = [
|
||||
fragmentFixture("text", "text-active", ["Active complete"]).completeEvents,
|
||||
[LLMEvent.textDelta({ id: "summary", text: "durable summary" })],
|
||||
fragmentFixture("text", "text-steer", ["Steer complete"]).completeEvents,
|
||||
fragmentFixture("text", "text-queue", ["Queue complete"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Active work" }), resume: false })
|
||||
const active = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
|
||||
const first = yield* session.compact({ sessionID })
|
||||
const second = yield* session.compact({ sessionID })
|
||||
expect(second.id).toBe(first.id)
|
||||
expect(yield* SessionInput.pendingCompaction((yield* Database.Service).db, sessionID)).toMatchObject({
|
||||
id: first.id,
|
||||
})
|
||||
expect((yield* session.messages({ sessionID })).find((message) => message.id === first.id)).toMatchObject({
|
||||
type: "compaction",
|
||||
status: "queued",
|
||||
})
|
||||
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Steer after compaction" }), resume: false })
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: Prompt.make({ text: "Queue after compaction" }),
|
||||
delivery: "queue",
|
||||
resume: false,
|
||||
})
|
||||
expect(yield* SessionInput.hasPending((yield* Database.Service).db, sessionID, "steer")).toBe(false)
|
||||
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
yield* Fiber.join(active)
|
||||
|
||||
expect(requests).toHaveLength(4)
|
||||
expect(userTexts(requests[1])[0]).toContain("Create a new anchored summary")
|
||||
expect(userTexts(requests[2])).toContain("Steer after compaction")
|
||||
expect(userTexts(requests[3])).toContain("Queue after compaction")
|
||||
expect(yield* SessionInput.pendingCompaction((yield* Database.Service).db, sessionID)).toBeUndefined()
|
||||
expect((yield* session.messages({ sessionID })).find((message) => message.id === first.id)).toMatchObject({
|
||||
type: "compaction",
|
||||
status: "completed",
|
||||
summary: "durable summary",
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("releases queued prompts when durable compaction fails", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
requests.length = 0
|
||||
currentModel = recoveryModel
|
||||
const session = yield* SessionV2.Service
|
||||
streamGate = yield* Deferred.make<void>()
|
||||
streamStarted = yield* Deferred.make<void>()
|
||||
responses = [
|
||||
fragmentFixture("text", "text-active-failure", ["Active complete"]).completeEvents,
|
||||
[],
|
||||
fragmentFixture("text", "text-after-failure", ["Continued"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Active work" }), resume: false })
|
||||
const active = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
|
||||
const compaction = yield* session.compact({ sessionID })
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: Prompt.make({ text: "Continue after failure" }),
|
||||
delivery: "queue",
|
||||
resume: false,
|
||||
})
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
yield* Fiber.join(active)
|
||||
|
||||
expect(requests).toHaveLength(3)
|
||||
expect(userTexts(requests[2])).toContain("Continue after failure")
|
||||
expect(yield* SessionInput.pendingCompaction((yield* Database.Service).db, sessionID)).toBeUndefined()
|
||||
expect((yield* session.messages({ sessionID })).find((message) => message.id === compaction.id)).toMatchObject({
|
||||
type: "compaction",
|
||||
status: "failed",
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("automatically compacts into a completed summary and retained recent turn", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
@@ -1279,7 +1422,7 @@ describe("SessionRunnerLLM", () => {
|
||||
currentModel = compactModel
|
||||
requests.length = 0
|
||||
responses = [
|
||||
fragmentFixture("text", "text-summary", ["## Goal\n- Preserve the task"]).completeEvents,
|
||||
fragmentFixture("text", "text-summary", ["## Continuation Goal\n- Preserve the task"]).completeEvents,
|
||||
fragmentFixture("text", "text-final", ["Continued"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({
|
||||
@@ -1290,22 +1433,22 @@ describe("SessionRunnerLLM", () => {
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
expect(userTexts(requests[0])[0]).toContain("## Goal")
|
||||
expect(userTexts(requests[0])[0]).toContain("## Continuation Goal")
|
||||
expect(userTexts(requests[1])).toHaveLength(1)
|
||||
expect(userTexts(requests[1])[0]).toContain("<summary>\n## Goal\n- Preserve the task\n</summary>")
|
||||
expect(userTexts(requests[1])[0]).toContain("<summary>\n## Continuation Goal\n- Preserve the task\n</summary>")
|
||||
expect(userTexts(requests[1])[0]).toContain(`[User]: ${"Recent exact request ".repeat(180)}`)
|
||||
|
||||
const context = yield* (yield* SessionStore.Service).context(sessionID)
|
||||
expect(context.map((message) => message.type)).toEqual(["compaction", "assistant"])
|
||||
expect(context[0]).toMatchObject({
|
||||
type: "compaction",
|
||||
summary: "## Goal\n- Preserve the task",
|
||||
summary: "## Continuation Goal\n- Preserve the task",
|
||||
})
|
||||
|
||||
requests.length = 0
|
||||
executions.length = 0
|
||||
responses = [
|
||||
fragmentFixture("text", "text-summary-2", ["## Goal\n- Preserve the updated task"]).completeEvents,
|
||||
fragmentFixture("text", "text-summary-2", ["## Continuation Goal\n- Preserve the updated task"]).completeEvents,
|
||||
fragmentFixture("text", "text-final-2", ["Continued again"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({
|
||||
@@ -1317,12 +1460,12 @@ describe("SessionRunnerLLM", () => {
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
expect(userTexts(requests[0])[0]).toContain(
|
||||
"<previous-summary>\n## Goal\n- Preserve the task\n</previous-summary>",
|
||||
"<previous-summary>\n## Continuation Goal\n- Preserve the task\n</previous-summary>",
|
||||
)
|
||||
expect(userTexts(requests[0])[0]).toContain("Recent exact request")
|
||||
expect((yield* (yield* SessionStore.Service).context(sessionID))[0]).toMatchObject({
|
||||
type: "compaction",
|
||||
summary: "## Goal\n- Preserve the updated task",
|
||||
summary: "## Continuation Goal\n- Preserve the updated task",
|
||||
})
|
||||
}),
|
||||
)
|
||||
@@ -1335,17 +1478,17 @@ describe("SessionRunnerLLM", () => {
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" }),
|
||||
],
|
||||
fragmentFixture("text", "text-summary", ["## Goal\n- Recover overflow"]).completeEvents,
|
||||
fragmentFixture("text", "text-summary", ["## Continuation Goal\n- Recover overflow"]).completeEvents,
|
||||
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(3)
|
||||
expect(userTexts(requests[1])[0]).toContain("## Goal")
|
||||
expect(userTexts(requests[2])[0]).toContain("<summary>\n## Goal\n- Recover overflow\n</summary>")
|
||||
expect(userTexts(requests[1])[0]).toContain("## Continuation Goal")
|
||||
expect(userTexts(requests[2])[0]).toContain("<summary>\n## Continuation Goal\n- Recover overflow\n</summary>")
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "compaction", summary: "## Goal\n- Recover overflow" },
|
||||
{ type: "compaction", summary: "## Continuation Goal\n- Recover overflow" },
|
||||
{ type: "assistant", finish: "stop" },
|
||||
])
|
||||
yield* replaySessionProjection(sessionID)
|
||||
@@ -1365,11 +1508,11 @@ describe("SessionRunnerLLM", () => {
|
||||
]
|
||||
responses = [
|
||||
overflow(),
|
||||
fragmentFixture("text", "text-summary", ["## Goal\n- Recover once"]).completeEvents,
|
||||
fragmentFixture("text", "text-summary", ["## Continuation Goal\n- Recover once"]).completeEvents,
|
||||
overflow(),
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("prompt too long")
|
||||
|
||||
expect(requests).toHaveLength(3)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
@@ -1393,7 +1536,7 @@ describe("SessionRunnerLLM", () => {
|
||||
}),
|
||||
)
|
||||
responses = [
|
||||
fragmentFixture("text", "text-summary", ["## Goal\n- Recover raw overflow"]).completeEvents,
|
||||
fragmentFixture("text", "text-summary", ["## Continuation Goal\n- Recover raw overflow"]).completeEvents,
|
||||
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
||||
@@ -1401,7 +1544,7 @@ describe("SessionRunnerLLM", () => {
|
||||
|
||||
expect(requests).toHaveLength(3)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "compaction", summary: "## Goal\n- Recover raw overflow" },
|
||||
{ type: "compaction", summary: "## Continuation Goal\n- Recover raw overflow" },
|
||||
{ type: "assistant", finish: "stop" },
|
||||
])
|
||||
}),
|
||||
@@ -1415,7 +1558,7 @@ describe("SessionRunnerLLM", () => {
|
||||
[LLMEvent.providerError({ message: "summary unavailable" })],
|
||||
]
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
||||
yield* session.resume(sessionID)
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("prompt too long")
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
const context = yield* session.context(sessionID)
|
||||
@@ -1432,7 +1575,7 @@ describe("SessionRunnerLLM", () => {
|
||||
const session = yield* setupOverflowRecovery
|
||||
responses = [
|
||||
[LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" })],
|
||||
fragmentFixture("text", "text-summary", ["## Goal\n- Interrupted"]).completeEvents,
|
||||
fragmentFixture("text", "text-summary", ["## Continuation Goal\n- Interrupted"]).completeEvents,
|
||||
]
|
||||
const firstGate = yield* Deferred.make<void>()
|
||||
const summaryGate = yield* Deferred.make<void>()
|
||||
@@ -1552,7 +1695,7 @@ describe("SessionRunnerLLM", () => {
|
||||
finish: "tool-calls",
|
||||
tokens: { input: 8, output: 3, reasoning: 1, cache: { read: 2, write: 0 } },
|
||||
content: [
|
||||
{ type: "reasoning", id: "reasoning-1", text: "Think" },
|
||||
{ type: "reasoning", text: "Think" },
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-error",
|
||||
@@ -1560,14 +1703,16 @@ describe("SessionRunnerLLM", () => {
|
||||
state: {
|
||||
status: "error",
|
||||
input: { path: "README.md" },
|
||||
error: { type: "unknown", message: "Denied" },
|
||||
error: { type: "tool.execution", message: "Denied" },
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-provider",
|
||||
name: "web_search",
|
||||
provider: { executed: true, metadata: { fake: { source: "provider" } } },
|
||||
executed: true,
|
||||
providerState: { source: "provider" },
|
||||
providerResultState: { source: "provider" },
|
||||
state: {
|
||||
status: "completed",
|
||||
input: { query: "hello" },
|
||||
@@ -1618,7 +1763,8 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "assistant", "tool"])
|
||||
expect(authorizations).toMatchObject([{ sessionID, toolCallID: "call-echo" }])
|
||||
expect(executions).toEqual(["hello"])
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
const context = yield* session.context(sessionID)
|
||||
expect(context).toMatchObject([
|
||||
{ type: "user", text: "Echo this" },
|
||||
{
|
||||
type: "assistant",
|
||||
@@ -1637,7 +1783,14 @@ describe("SessionRunnerLLM", () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", id: "text-final", text: "Done" }] },
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", text: "Done" }] },
|
||||
])
|
||||
const assistant = requireAssistant(context)
|
||||
expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.success.1",
|
||||
"session.step.ended.1",
|
||||
])
|
||||
}),
|
||||
)
|
||||
@@ -1696,15 +1849,24 @@ describe("SessionRunnerLLM", () => {
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.reasoningStart({ id: "reasoning-anthropic" }),
|
||||
LLMEvent.reasoningDelta({ id: "reasoning-anthropic", text: "Signed thought" }),
|
||||
LLMEvent.reasoningEnd({ id: "reasoning-anthropic", providerMetadata: { anthropic: { signature: "sig_1" } } }),
|
||||
LLMEvent.reasoningEnd({
|
||||
id: "reasoning-anthropic",
|
||||
providerMetadata: { fake: { signature: "sig_1" }, anthropic: { ignored: true } },
|
||||
}),
|
||||
LLMEvent.reasoningStart({
|
||||
id: "reasoning-openai",
|
||||
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: null } },
|
||||
providerMetadata: {
|
||||
fake: { itemId: "rs_1", reasoningEncryptedContent: null },
|
||||
openai: { ignored: true },
|
||||
},
|
||||
}),
|
||||
LLMEvent.reasoningDelta({ id: "reasoning-openai", text: "Encrypted thought" }),
|
||||
LLMEvent.reasoningEnd({
|
||||
id: "reasoning-openai",
|
||||
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
providerMetadata: {
|
||||
fake: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
openai: { ignored: true },
|
||||
},
|
||||
}),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "stop" }),
|
||||
LLMEvent.finish({ reason: "stop" }),
|
||||
@@ -1717,11 +1879,11 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "assistant",
|
||||
content: [
|
||||
{ type: "reasoning", text: "Signed thought", providerMetadata: { anthropic: { signature: "sig_1" } } },
|
||||
{ type: "reasoning", text: "Signed thought", state: { signature: "sig_1" } },
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Encrypted thought",
|
||||
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
state: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1732,11 +1894,11 @@ describe("SessionRunnerLLM", () => {
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
expect(requests[1]?.messages[1]?.content).toEqual([
|
||||
{ type: "reasoning", text: "Signed thought", providerMetadata: { anthropic: { signature: "sig_1" } } },
|
||||
{ type: "reasoning", text: "Signed thought", providerMetadata: { fake: { signature: "sig_1" } } },
|
||||
{
|
||||
type: "reasoning",
|
||||
text: "Encrypted thought",
|
||||
providerMetadata: { openai: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
providerMetadata: { fake: { itemId: "rs_1", reasoningEncryptedContent: "encrypted-state" } },
|
||||
},
|
||||
])
|
||||
}),
|
||||
@@ -1756,14 +1918,14 @@ describe("SessionRunnerLLM", () => {
|
||||
name: "web_search",
|
||||
input: { query: "Effect" },
|
||||
providerExecuted: true,
|
||||
providerMetadata: { openai: { itemId: "hosted-search" } },
|
||||
providerMetadata: { fake: { itemId: "hosted-search" }, openai: { ignored: true } },
|
||||
}),
|
||||
LLMEvent.toolResult({
|
||||
id: "hosted-search",
|
||||
name: "web_search",
|
||||
result: { type: "json", value: [{ title: "Effect" }] },
|
||||
providerExecuted: true,
|
||||
providerMetadata: { anthropic: { blockType: "web_search_tool_result" } },
|
||||
providerMetadata: { fake: { blockType: "web_search_tool_result" }, anthropic: { ignored: true } },
|
||||
}),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "stop" }),
|
||||
LLMEvent.finish({ reason: "stop" }),
|
||||
@@ -1783,7 +1945,7 @@ describe("SessionRunnerLLM", () => {
|
||||
name: "web_search",
|
||||
input: { query: "Effect" },
|
||||
providerExecuted: true,
|
||||
providerMetadata: { openai: { itemId: "hosted-search" } },
|
||||
providerMetadata: { fake: { itemId: "hosted-search" } },
|
||||
},
|
||||
{
|
||||
type: "tool-result",
|
||||
@@ -1791,7 +1953,7 @@ describe("SessionRunnerLLM", () => {
|
||||
name: "web_search",
|
||||
result: { type: "json", value: [{ title: "Effect" }] },
|
||||
providerExecuted: true,
|
||||
providerMetadata: { anthropic: { blockType: "web_search_tool_result" } },
|
||||
providerMetadata: { fake: { blockType: "web_search_tool_result" } },
|
||||
},
|
||||
])
|
||||
}),
|
||||
@@ -1980,7 +2142,7 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Run once" },
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", id: "text-once", text: "Once" }] },
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", text: "Once" }] },
|
||||
])
|
||||
}),
|
||||
)
|
||||
@@ -2352,7 +2514,7 @@ describe("SessionRunnerLLM", () => {
|
||||
requests.length = 0
|
||||
responses = undefined
|
||||
response = []
|
||||
streamFailure = providerUnavailable()
|
||||
streamFailure = invalidRequest()
|
||||
streamGate = yield* Deferred.make<void>()
|
||||
streamStarted = yield* Deferred.make<void>()
|
||||
|
||||
@@ -2402,9 +2564,8 @@ describe("SessionRunnerLLM", () => {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
callID: "call-interrupted",
|
||||
tool: "echo",
|
||||
input: { text: "stale" },
|
||||
provider: { executed: false },
|
||||
executed: false,
|
||||
})
|
||||
requests.length = 0
|
||||
response = []
|
||||
@@ -2420,7 +2581,10 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-interrupted",
|
||||
state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
|
||||
state: {
|
||||
status: "error",
|
||||
error: { type: "tool.stale", message: "Tool execution interrupted: echo" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -2462,9 +2626,9 @@ describe("SessionRunnerLLM", () => {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
callID: "call-hosted-interrupted",
|
||||
tool: "web_search",
|
||||
input: { query: "stale" },
|
||||
provider: { executed: true, metadata: { openai: { itemId: "call-hosted-interrupted" } } },
|
||||
executed: true,
|
||||
state: { itemId: "call-hosted-interrupted" },
|
||||
})
|
||||
requests.length = 0
|
||||
response = []
|
||||
@@ -2477,7 +2641,7 @@ describe("SessionRunnerLLM", () => {
|
||||
type: "tool-call",
|
||||
id: "call-hosted-interrupted",
|
||||
providerExecuted: true,
|
||||
providerMetadata: { openai: { itemId: "call-hosted-interrupted" } },
|
||||
providerMetadata: { fake: { itemId: "call-hosted-interrupted" } },
|
||||
},
|
||||
{ type: "tool-result", id: "call-hosted-interrupted", providerExecuted: true, result: { type: "error" } },
|
||||
])
|
||||
@@ -2663,7 +2827,7 @@ describe("SessionRunnerLLM", () => {
|
||||
requests.length = 0
|
||||
responses = undefined
|
||||
response = []
|
||||
streamFailure = providerUnavailable()
|
||||
streamFailure = invalidRequest()
|
||||
streamGate = yield* Deferred.make<void>()
|
||||
streamStarted = yield* Deferred.make<void>()
|
||||
|
||||
@@ -2726,7 +2890,7 @@ describe("SessionRunnerLLM", () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", id: "text-after-error", text: "Recovered" }] },
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", text: "Recovered" }] },
|
||||
])
|
||||
}),
|
||||
)
|
||||
@@ -2759,7 +2923,8 @@ describe("SessionRunnerLLM", () => {
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "assistant", "tool"])
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
const context = yield* session.context(sessionID)
|
||||
expect(context).toMatchObject([
|
||||
{ type: "user", text: "Call defect" },
|
||||
{
|
||||
type: "assistant",
|
||||
@@ -2769,13 +2934,20 @@ describe("SessionRunnerLLM", () => {
|
||||
id: "call-defect",
|
||||
state: {
|
||||
status: "error",
|
||||
error: { type: "unknown", message: "Tool execution failed: unexpected tool defect" },
|
||||
error: { type: "unknown", message: "unexpected tool defect" },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", text: "Recovered" }] },
|
||||
])
|
||||
const assistant = requireAssistant(context)
|
||||
expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.ended.1",
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -2812,13 +2984,76 @@ describe("SessionRunnerLLM", () => {
|
||||
status: "error",
|
||||
error: {
|
||||
type: "unknown",
|
||||
message: expect.stringContaining("Tool execution failed: Failed to encode tool output"),
|
||||
message: expect.stringContaining("Failed to encode tool output"),
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
finish: "error",
|
||||
error: { type: "unknown", message: expect.stringContaining("Failed to encode tool output") },
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("preserves permission rejection and stops before continuation", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
const registry = yield* ToolRegistry.Service
|
||||
yield* registry.register({
|
||||
permissionfail: Tool.make({
|
||||
description: "Reject a permission",
|
||||
input: Schema.Struct({}),
|
||||
output: Schema.Struct({}),
|
||||
execute: () =>
|
||||
new ToolFailure({
|
||||
message: "Permission rejected: edit",
|
||||
error: new PermissionV2.RejectedError({ permission: "edit", resources: ["src/index.ts"] }),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Reject permission" }), resume: false })
|
||||
requests.length = 0
|
||||
responses = [
|
||||
[
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolCall({ id: "call-permission", name: "permissionfail", input: {} }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "tool-calls" }),
|
||||
LLMEvent.finish({ reason: "tool-calls" }),
|
||||
],
|
||||
[LLMEvent.stepStart({ index: 0 }), LLMEvent.stepFinish({ index: 0, reason: "stop" })],
|
||||
]
|
||||
|
||||
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
||||
|
||||
expect(exit._tag).toBe("Failure")
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user" },
|
||||
{
|
||||
type: "assistant",
|
||||
finish: "error",
|
||||
error: {
|
||||
type: "permission.rejected",
|
||||
message: "Permission rejected: edit",
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-permission",
|
||||
state: {
|
||||
status: "error",
|
||||
error: {
|
||||
type: "permission.rejected",
|
||||
message: "Permission rejected: edit",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
expect(yield* recordedEventTypes(sessionID)).not.toContain("session.step.ended.1")
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -2860,7 +3095,8 @@ describe("SessionRunnerLLM", () => {
|
||||
const exit = yield* Fiber.join(run)
|
||||
|
||||
expect(exit._tag).toBe("Failure")
|
||||
if (exit._tag === "Failure") expect(Cause.hasInterruptsOnly(exit.cause)).toBe(true)
|
||||
if (exit._tag === "Failure")
|
||||
expect(Option.getOrUndefined(Cause.findErrorOption(exit.cause))).toBeInstanceOf(UserInterruptedError)
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Ask then stop" },
|
||||
@@ -2870,7 +3106,7 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-question",
|
||||
state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
|
||||
state: { status: "error", error: { type: "aborted", message: "Tool execution interrupted" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -2900,7 +3136,8 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(failure)
|
||||
toolExecutionGate = undefined
|
||||
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
const context = yield* session.context(sessionID)
|
||||
expect(context).toMatchObject([
|
||||
{ type: "user", text: "Settle before failing" },
|
||||
{
|
||||
type: "assistant",
|
||||
@@ -2909,6 +3146,13 @@ describe("SessionRunnerLLM", () => {
|
||||
],
|
||||
},
|
||||
])
|
||||
const assistant = requireAssistant(context)
|
||||
expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.success.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -2934,7 +3178,8 @@ describe("SessionRunnerLLM", () => {
|
||||
|
||||
expect(yield* Fiber.await(run)).toMatchObject({ _tag: "Failure" })
|
||||
yield* session.interrupt(sessionID)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
const context = yield* session.context(sessionID)
|
||||
expect(context).toMatchObject([
|
||||
{ type: "user", text: "Interrupt blocked tool" },
|
||||
{
|
||||
type: "assistant",
|
||||
@@ -2942,11 +3187,18 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-before-interrupt",
|
||||
state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
|
||||
state: { status: "error", error: { type: "aborted", message: "Tool execution interrupted" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
const assistant = requireAssistant(context)
|
||||
expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
|
||||
yield* replaySessionProjection(sessionID)
|
||||
|
||||
@@ -2983,7 +3235,7 @@ describe("SessionRunnerLLM", () => {
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Interrupt provider" },
|
||||
{ type: "assistant", finish: "error", error: { type: "unknown", message: "Step interrupted" } },
|
||||
{ type: "assistant", finish: "error", error: { type: "aborted", message: "Step interrupted" } },
|
||||
])
|
||||
expect(yield* recordedEventTypes(sessionID)).toContain("session.step.failed.1")
|
||||
yield* session.interrupt(sessionID)
|
||||
@@ -3018,12 +3270,12 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "assistant",
|
||||
finish: "error",
|
||||
error: { type: "unknown", message: "Step interrupted" },
|
||||
error: { type: "aborted", message: "Step interrupted" },
|
||||
content: [
|
||||
{
|
||||
type: "tool",
|
||||
id: "call-await-interrupt",
|
||||
state: { status: "error", error: { type: "unknown", message: "Tool execution interrupted" } },
|
||||
state: { status: "error", error: { type: "aborted", message: "Tool execution interrupted" } },
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -3146,12 +3398,12 @@ describe("SessionRunnerLLM", () => {
|
||||
streamStarted = undefined
|
||||
response = [LLMEvent.stepStart({ index: 0 }), LLMEvent.providerError({ message: "Provider unavailable" })]
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable")
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Fail durably" },
|
||||
{ type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } },
|
||||
{ type: "assistant", finish: "error", error: { type: "provider.unknown", message: "Provider unavailable" } },
|
||||
])
|
||||
}),
|
||||
)
|
||||
@@ -3165,16 +3417,79 @@ describe("SessionRunnerLLM", () => {
|
||||
requests.length = 0
|
||||
response = [LLMEvent.providerError({ message: "Provider unavailable" })]
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable")
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Fail before step" },
|
||||
{ type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } },
|
||||
{ type: "assistant", finish: "error", error: { type: "provider.unknown", message: "Provider unavailable" } },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("projects content-filter finishes as visible terminal failures", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Blocked response" }), resume: false })
|
||||
response = [
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.textStart({ id: "partial" }),
|
||||
LLMEvent.textDelta({ id: "partial", text: "Partial" }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "content-filter" }),
|
||||
LLMEvent.finish({ reason: "content-filter" }),
|
||||
]
|
||||
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider blocked the response")
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user" },
|
||||
{
|
||||
type: "assistant",
|
||||
finish: "error",
|
||||
error: { type: "provider.content-filter" },
|
||||
content: [{ type: "text", text: "Partial" }],
|
||||
},
|
||||
])
|
||||
expect(yield* recordedEventTypes(sessionID)).not.toContain("session.step.ended.1")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("settles a local tool before one content-filter step failure", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Tool before blocked response" }), resume: false })
|
||||
toolExecutionGate = yield* Deferred.make<void>()
|
||||
toolExecutionsStarted = yield* Deferred.make<void>()
|
||||
toolExecutionsReady = 1
|
||||
response = [
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolCall({ id: "call-before-content-filter", name: "echo", input: { text: "settled" } }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "content-filter" }),
|
||||
LLMEvent.finish({ reason: "content-filter" }),
|
||||
]
|
||||
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(toolExecutionsStarted)
|
||||
yield* Deferred.succeed(toolExecutionGate, undefined)
|
||||
expect((yield* Fiber.join(run).pipe(Effect.flip)).message).toBe("Provider blocked the response")
|
||||
toolExecutionGate = undefined
|
||||
toolExecutionsStarted = undefined
|
||||
|
||||
const assistant = requireAssistant(yield* session.context(sessionID))
|
||||
const events = yield* recordedStepSettlementEvents(sessionID, assistant.id)
|
||||
expect(events.map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.success.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
expect(
|
||||
events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"),
|
||||
).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("does not recover context overflow after durable assistant output", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
@@ -3189,7 +3504,7 @@ describe("SessionRunnerLLM", () => {
|
||||
LLMEvent.textEnd({ id: "text-partial" }),
|
||||
LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" }),
|
||||
]
|
||||
yield* session.resume(sessionID)
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("prompt too long")
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
@@ -3209,18 +3524,146 @@ describe("SessionRunnerLLM", () => {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail raw stream durably" }), resume: false })
|
||||
const failure = providerUnavailable()
|
||||
const failure = invalidRequest()
|
||||
responseStream = Stream.fail(failure)
|
||||
|
||||
expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure)
|
||||
yield* replaySessionProjection(sessionID)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Fail raw stream durably" },
|
||||
{ type: "assistant", finish: "error", error: { type: "unknown", message: "Provider unavailable" } },
|
||||
{ type: "assistant", finish: "error", error: { type: "provider.invalid-request", message: "Invalid request" } },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("retries eligible pre-output failures after exponential backoff", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Retry transport" }), resume: false })
|
||||
requests.length = 0
|
||||
responseStream = Stream.fail(providerUnavailable())
|
||||
response = fragmentFixture("text", "retry-success", ["Recovered"]).completeEvents
|
||||
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
while (requests.length < 1) yield* Effect.yieldNow
|
||||
yield* TestClock.adjust("1999 millis")
|
||||
expect(requests).toHaveLength(1)
|
||||
yield* TestClock.adjust("1 millis")
|
||||
yield* Fiber.join(run)
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
const eventTypes = yield* recordedEventTypes(sessionID)
|
||||
expect(eventTypes).toContain("session.retry.scheduled.1")
|
||||
expect(eventTypes.filter((type) => type === "session.step.started.1")).toHaveLength(2)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user" },
|
||||
{ type: "assistant", finish: "stop", content: [{ type: "text", text: "Recovered" }] },
|
||||
])
|
||||
yield* replaySessionProjection(sessionID)
|
||||
expect((yield* session.context(sessionID)).filter((message) => message.type === "assistant")).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("uses a larger provider retry-after delay", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Retry rate limit" }), resume: false })
|
||||
requests.length = 0
|
||||
responseStream = Stream.fail(rateLimited(5_000))
|
||||
response = fragmentFixture("text", "retry-after-success", ["Recovered"]).completeEvents
|
||||
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
while (requests.length < 1) yield* Effect.yieldNow
|
||||
yield* TestClock.adjust("4999 millis")
|
||||
expect(requests).toHaveLength(1)
|
||||
yield* TestClock.adjust("1 millis")
|
||||
yield* Fiber.join(run)
|
||||
expect(requests).toHaveLength(2)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("stops after five total retry attempts", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Exhaust retries" }), resume: false })
|
||||
requests.length = 0
|
||||
streamFailure = providerUnavailable()
|
||||
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
while (requests.length < 1) yield* Effect.yieldNow
|
||||
for (const [index, delay] of [2_000, 4_000, 8_000, 16_000].entries()) {
|
||||
yield* TestClock.adjust(delay)
|
||||
while (requests.length < index + 2) yield* Effect.yieldNow
|
||||
}
|
||||
expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(streamFailure)
|
||||
expect(requests).toHaveLength(5)
|
||||
|
||||
const database = (yield* Database.Service).db
|
||||
const retries = yield* database
|
||||
.select({ data: EventTable.data })
|
||||
.from(EventTable)
|
||||
.where(eq(EventTable.type, "session.retry.scheduled.1"))
|
||||
.orderBy(asc(EventTable.seq))
|
||||
.all()
|
||||
.pipe(Effect.orDie)
|
||||
expect(retries.map((event) => event.data)).toMatchObject([
|
||||
{ attempt: 2, at: 2_000 },
|
||||
{ attempt: 3, at: 6_000 },
|
||||
{ attempt: 4, at: 14_000 },
|
||||
{ attempt: 5, at: 30_000 },
|
||||
])
|
||||
expect((yield* recordedEventTypes(sessionID)).filter((type) => type === "session.step.started.1")).toHaveLength(5)
|
||||
expect((yield* session.context(sessionID)).filter((message) => message.type === "assistant")).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("counts retry attempts against the agent step allowance", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const agents = yield* AgentV2.Service
|
||||
yield* agents.transform((editor) =>
|
||||
editor.update(AgentV2.ID.make("build"), (agent) => {
|
||||
agent.steps = 2
|
||||
}),
|
||||
)
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Bound retries by steps" }), resume: false })
|
||||
requests.length = 0
|
||||
const failure = providerUnavailable()
|
||||
responseStream = Stream.fail(failure)
|
||||
streamFailure = failure
|
||||
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
while (requests.length < 1) yield* Effect.yieldNow
|
||||
yield* TestClock.adjust("2 seconds")
|
||||
expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(failure)
|
||||
|
||||
expect(requests).toHaveLength(2)
|
||||
const eventTypes = yield* recordedEventTypes(sessionID)
|
||||
expect(eventTypes.filter((type) => type === "session.step.started.1")).toHaveLength(2)
|
||||
expect(eventTypes.filter((type) => type === "session.retry.scheduled.1")).toHaveLength(1)
|
||||
expect((yield* session.context(sessionID)).filter((message) => message.type === "assistant")).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("does not retry non-eligible provider failures", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Do not retry" }), resume: false })
|
||||
requests.length = 0
|
||||
const failure = invalidRequest()
|
||||
streamFailure = failure
|
||||
|
||||
expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure)
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* recordedEventTypes(sessionID)).not.toContain("session.retry.scheduled.1")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("does not continue automatically after a provider error follows a local tool call", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
@@ -3233,16 +3676,32 @@ describe("SessionRunnerLLM", () => {
|
||||
|
||||
requests.length = 0
|
||||
const executionCount = executions.length
|
||||
toolExecutionGate = yield* Deferred.make<void>()
|
||||
toolExecutionsStarted = yield* Deferred.make<void>()
|
||||
toolExecutionsReady = 1
|
||||
response = [
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolCall({ id: "call-before-provider-error", name: "echo", input: { text: "settled" } }),
|
||||
LLMEvent.providerError({ message: "Provider unavailable" }),
|
||||
]
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(toolExecutionsStarted)
|
||||
yield* Deferred.succeed(toolExecutionGate, undefined)
|
||||
expect((yield* Fiber.join(run).pipe(Effect.flip)).message).toBe("Provider unavailable")
|
||||
toolExecutionGate = undefined
|
||||
toolExecutionsStarted = undefined
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(executions.slice(executionCount)).toEqual(["settled"])
|
||||
const context = yield* session.context(sessionID)
|
||||
const assistant = requireAssistant(context)
|
||||
expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.success.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -3264,16 +3723,50 @@ describe("SessionRunnerLLM", () => {
|
||||
LLMEvent.providerError({ message: "Provider unavailable" }),
|
||||
]
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable")
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
const context = yield* session.context(sessionID)
|
||||
expect(context).toMatchObject([
|
||||
{ type: "user", text: "Fail hosted tool durably" },
|
||||
{
|
||||
type: "assistant",
|
||||
content: [{ type: "tool", id: "call-hosted-provider-error", state: { status: "error" } }],
|
||||
},
|
||||
])
|
||||
const assistant = requireAssistant(context)
|
||||
expect((yield* recordedStepSettlementEvents(sessionID, assistant.id)).map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("preserves a tool defect before provider failure settlement", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Defect while provider fails" }), resume: false })
|
||||
response = [
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolCall({ id: "call-defect-provider-error", name: "defect", input: {} }),
|
||||
LLMEvent.providerError({ message: "Provider unavailable" }),
|
||||
]
|
||||
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider unavailable")
|
||||
|
||||
const context = yield* session.context(sessionID)
|
||||
const assistant = requireAssistant(context)
|
||||
const events = yield* recordedStepSettlementEvents(sessionID, assistant.id)
|
||||
expect(events.map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
expect(events[2]?.data.error).toMatchObject({ type: "unknown", message: "unexpected tool defect" })
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -3292,16 +3785,113 @@ describe("SessionRunnerLLM", () => {
|
||||
}),
|
||||
]
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("Provider did not return a tool result")
|
||||
const assistant = requireAssistant(yield* session.context(sessionID))
|
||||
const events = yield* recordedStepSettlementEvents(sessionID, assistant.id)
|
||||
expect(events.map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
expect(
|
||||
events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"),
|
||||
).toHaveLength(1)
|
||||
yield* replaySessionProjection(sessionID)
|
||||
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Fail hosted tool at EOF" },
|
||||
{ type: "assistant", content: [{ type: "tool", id: "call-hosted-eof", state: { status: "error" } }] },
|
||||
{
|
||||
type: "assistant",
|
||||
finish: "error",
|
||||
error: { type: "tool.result-missing" },
|
||||
content: [{ type: "tool", id: "call-hosted-eof", state: { status: "error" } }],
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("fails an unresolved hosted tool before one clean step end", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: Prompt.make({ text: "Settle hosted tool before ending" }),
|
||||
resume: false,
|
||||
})
|
||||
response = [
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolCall({
|
||||
id: "call-hosted-clean-end",
|
||||
name: "web_search",
|
||||
input: { query: "effect" },
|
||||
providerExecuted: true,
|
||||
}),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "stop" }),
|
||||
LLMEvent.finish({ reason: "stop" }),
|
||||
]
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
const assistant = requireAssistant(yield* session.context(sessionID))
|
||||
const events = yield* recordedStepSettlementEvents(sessionID, assistant.id)
|
||||
expect(events.map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.ended.1",
|
||||
])
|
||||
expect(
|
||||
events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"),
|
||||
).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("settles unresolved local and hosted tools before one raw provider failure", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail unresolved tools" }), resume: false })
|
||||
const failure = invalidRequest()
|
||||
const providerFailed = yield* Deferred.make<void>()
|
||||
toolExecutionGate = yield* Deferred.make<void>()
|
||||
responseStream = Stream.concat(
|
||||
Stream.fromIterable([
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.toolCall({ id: "call-local-raw-failure", name: "defect", input: {} }),
|
||||
LLMEvent.toolCall({
|
||||
id: "call-hosted-raw-failure-pair",
|
||||
name: "web_search",
|
||||
input: { query: "effect" },
|
||||
providerExecuted: true,
|
||||
}),
|
||||
]),
|
||||
Stream.fromEffect(Deferred.succeed(providerFailed, undefined)).pipe(Stream.flatMap(() => Stream.fail(failure))),
|
||||
)
|
||||
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(providerFailed)
|
||||
yield* Deferred.succeed(toolExecutionGate, undefined)
|
||||
expect(yield* Fiber.join(run).pipe(Effect.flip)).toBe(failure)
|
||||
toolExecutionGate = undefined
|
||||
|
||||
const assistant = requireAssistant(yield* session.context(sessionID))
|
||||
const events = yield* recordedStepSettlementEvents(sessionID, assistant.id)
|
||||
expect(events.map((event) => ({ type: event.type, callID: event.data.callID }))).toEqual([
|
||||
{ type: "session.step.started.1", callID: undefined },
|
||||
{ type: "session.tool.called.1", callID: "call-local-raw-failure" },
|
||||
{ type: "session.tool.called.1", callID: "call-hosted-raw-failure-pair" },
|
||||
{ type: "session.tool.failed.1", callID: "call-local-raw-failure" },
|
||||
{ type: "session.tool.failed.1", callID: "call-hosted-raw-failure-pair" },
|
||||
{ type: "session.step.failed.1", callID: undefined },
|
||||
])
|
||||
expect(
|
||||
events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"),
|
||||
).toHaveLength(1)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("durably fails a hosted tool left unresolved by a raw provider stream failure", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
@@ -3311,6 +3901,7 @@ describe("SessionRunnerLLM", () => {
|
||||
prompt: Prompt.make({ text: "Fail hosted tool on raw failure" }),
|
||||
resume: false,
|
||||
})
|
||||
requests.length = 0
|
||||
const failure = providerUnavailable()
|
||||
responseStream = Stream.concat(
|
||||
Stream.fromIterable([
|
||||
@@ -3326,20 +3917,32 @@ describe("SessionRunnerLLM", () => {
|
||||
)
|
||||
|
||||
expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure)
|
||||
expect(requests).toHaveLength(1)
|
||||
const assistant = requireAssistant(yield* session.context(sessionID))
|
||||
const events = yield* recordedStepSettlementEvents(sessionID, assistant.id)
|
||||
expect(events.map((event) => event.type)).toEqual([
|
||||
"session.step.started.1",
|
||||
"session.tool.called.1",
|
||||
"session.tool.failed.1",
|
||||
"session.step.failed.1",
|
||||
])
|
||||
expect(
|
||||
events.filter((event) => event.type.startsWith("session.step.") && event.type !== "session.step.started.1"),
|
||||
).toHaveLength(1)
|
||||
yield* replaySessionProjection(sessionID)
|
||||
expect(yield* session.context(sessionID)).toMatchObject([
|
||||
{ type: "user", text: "Fail hosted tool on raw failure" },
|
||||
{
|
||||
type: "assistant",
|
||||
finish: "error",
|
||||
error: { type: "unknown", message: "Provider unavailable" },
|
||||
error: { type: "provider.transport", message: "Provider unavailable" },
|
||||
content: [{ type: "tool", id: "call-hosted-raw-failure", state: { status: "error" } }],
|
||||
},
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("keeps interleaved assistant text blocks separate", () =>
|
||||
it.effect("rejects a second text start before the open fragment ends", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
@@ -3352,9 +3955,31 @@ describe("SessionRunnerLLM", () => {
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.textStart({ id: "text-1" }),
|
||||
LLMEvent.textStart({ id: "text-2" }),
|
||||
]
|
||||
|
||||
const defect = yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed))
|
||||
expect(defect).toBeInstanceOf(Error)
|
||||
if (!(defect instanceof Error)) return
|
||||
expect(defect.message).toBe("text start before end: text-2")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("projects sequential text fragments as separate content parts", () =>
|
||||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Two blocks" }), resume: false })
|
||||
|
||||
responses = undefined
|
||||
streamGate = undefined
|
||||
streamStarted = undefined
|
||||
response = [
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
LLMEvent.textStart({ id: "text-1" }),
|
||||
LLMEvent.textDelta({ id: "text-1", text: "First" }),
|
||||
LLMEvent.textDelta({ id: "text-2", text: "Second" }),
|
||||
LLMEvent.textEnd({ id: "text-1" }),
|
||||
LLMEvent.textStart({ id: "text-2" }),
|
||||
LLMEvent.textDelta({ id: "text-2", text: "Second" }),
|
||||
LLMEvent.textEnd({ id: "text-2" }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "stop" }),
|
||||
LLMEvent.finish({ reason: "stop" }),
|
||||
@@ -3367,8 +3992,8 @@ describe("SessionRunnerLLM", () => {
|
||||
{
|
||||
type: "assistant",
|
||||
content: [
|
||||
{ type: "text", id: "text-1", text: "First" },
|
||||
{ type: "text", id: "text-2", text: "Second" },
|
||||
{ type: "text", text: "First" },
|
||||
{ type: "text", text: "Second" },
|
||||
],
|
||||
},
|
||||
])
|
||||
@@ -3418,6 +4043,8 @@ describe("SessionRunnerLLM", () => {
|
||||
LLMEvent.toolInputDelta({ id: "call-parsed", name: "web_search", text: '{"query":"hello"}' }),
|
||||
LLMEvent.toolInputEnd({ id: "call-parsed", name: "web_search" }),
|
||||
LLMEvent.toolCall({ id: "call-parsed", name: "web_search", input: { query: "hello" }, providerExecuted: true }),
|
||||
LLMEvent.stepFinish({ index: 0, reason: "stop" }),
|
||||
LLMEvent.finish({ reason: "stop" }),
|
||||
]
|
||||
|
||||
yield* session.resume(sessionID)
|
||||
|
||||
@@ -76,9 +76,8 @@ describe("Tool.Progress", () => {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
callID,
|
||||
tool: "bash",
|
||||
input: { command: "pwd" },
|
||||
provider: { executed: false },
|
||||
executed: false,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -104,7 +103,7 @@ describe("Tool.Progress", () => {
|
||||
callID: "call-success",
|
||||
structured: { phase: "done" },
|
||||
content: content("complete"),
|
||||
provider: { executed: false },
|
||||
executed: false,
|
||||
})
|
||||
expect((yield* readAssistant).content[0]).toMatchObject({
|
||||
state: { status: "completed", structured: { phase: "done" }, content: content("complete") },
|
||||
@@ -123,7 +122,7 @@ describe("Tool.Progress", () => {
|
||||
assistantMessageID,
|
||||
callID: "call-failed",
|
||||
error: { type: "unknown", message: "boom" },
|
||||
provider: { executed: false },
|
||||
executed: false,
|
||||
})
|
||||
expect((yield* readAssistant).content[1]).toMatchObject({
|
||||
state: {
|
||||
|
||||
@@ -107,6 +107,7 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||
[coreIntegration.Ref, Integration.Ref],
|
||||
[coreLocation.Ref, Location.Ref],
|
||||
[coreLLM.ProviderMetadata, LLM.ProviderMetadata],
|
||||
[coreLLM.FinishReason, LLM.FinishReason],
|
||||
[coreLLM.ToolTextContent, LLM.ToolTextContent],
|
||||
[coreLLM.ToolFileContent, LLM.ToolFileContent],
|
||||
[coreLLM.ToolContent, LLM.ToolContent],
|
||||
@@ -147,7 +148,7 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||
[coreSessionInput.Delivery, SessionInput.Delivery],
|
||||
[coreSessionInput.Admitted, SessionInput.Admitted],
|
||||
[coreSessionMessage.ID, SessionMessage.ID],
|
||||
[coreSessionMessage.UnknownError, SessionMessage.UnknownError],
|
||||
[coreSessionMessage.AssistantRetry, SessionMessage.AssistantRetry],
|
||||
[coreSessionMessage.AgentSelected, SessionMessage.AgentSelected],
|
||||
[coreSessionMessage.ModelSelected, SessionMessage.ModelSelected],
|
||||
[coreSessionMessage.User, SessionMessage.User],
|
||||
@@ -197,7 +198,7 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||
test("shared record schemas construct and decode plain objects", () => {
|
||||
const made = Prompt.make({ text: "hello" })
|
||||
const decoded = Schema.decodeUnknownSync(Prompt)({ text: "hello" })
|
||||
const content = Schema.decodeUnknownSync(SessionMessage.AssistantText)({ type: "text", id: "part_1", text: "hi" })
|
||||
const content = Schema.decodeUnknownSync(SessionMessage.AssistantText)({ type: "text", text: "hi" })
|
||||
|
||||
expect(Object.getPrototypeOf(made)).toBe(Object.prototype)
|
||||
expect(Object.getPrototypeOf(decoded)).toBe(Object.prototype)
|
||||
|
||||
@@ -47,7 +47,15 @@ const permission = Layer.succeed(
|
||||
}).pipe(
|
||||
Effect.andThen(input.action === "edit" ? Effect.suspend(afterEditApproval) : Effect.void),
|
||||
Effect.andThen(
|
||||
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
|
||||
input.action === denyAction
|
||||
? Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
|
||||
@@ -40,7 +40,15 @@ const permission = Layer.succeed(
|
||||
assert: (input) =>
|
||||
Effect.sync(() => assertions.push(input)).pipe(
|
||||
Effect.andThen(
|
||||
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
|
||||
input.action === denyAction
|
||||
? Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
|
||||
@@ -23,7 +23,17 @@ const permission = Layer.succeed(
|
||||
PermissionV2.Service.of({
|
||||
assert: (input) =>
|
||||
Effect.sync(() => assertions.push(input)).pipe(
|
||||
Effect.andThen(deny ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void),
|
||||
Effect.andThen(
|
||||
deny
|
||||
? Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
reply: () => Effect.die("unused"),
|
||||
@@ -72,7 +82,13 @@ describe("QuestionTool", () => {
|
||||
...toolIdentity,
|
||||
call: { type: "tool-call", id: "call-question-denied", name: "question", input: { questions: [] } },
|
||||
}),
|
||||
).toEqual({ result: { type: "error", value: "Permission denied: question" } })
|
||||
).toEqual({
|
||||
result: { type: "error", value: "Permission denied: question" },
|
||||
error: {
|
||||
type: "permission.rejected",
|
||||
message: "Permission denied: question",
|
||||
},
|
||||
})
|
||||
expect(capturedInput()).toBeUndefined()
|
||||
deny = false
|
||||
}),
|
||||
|
||||
@@ -81,7 +81,19 @@ const permission = Layer.succeed(
|
||||
assert: (input) =>
|
||||
Effect.sync(() => {
|
||||
assertions.push(input)
|
||||
}).pipe(Effect.andThen(allow ? Effect.void : Effect.fail(new PermissionV2.DeniedError({ rules: [] })))),
|
||||
}).pipe(
|
||||
Effect.andThen(
|
||||
allow
|
||||
? Effect.void
|
||||
: Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
reply: () => Effect.die("unused"),
|
||||
get: () => Effect.die("unused"),
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { makeLocationNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { PermissionV2 } from "@opencode-ai/core/permission"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { GlobTool } from "@opencode-ai/core/tool/glob"
|
||||
import { GrepTool } from "@opencode-ai/core/tool/grep"
|
||||
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
|
||||
import { ToolOutputStore } from "@opencode-ai/core/tool-output-store"
|
||||
import { location } from "./fixture/location"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
import { testEffect } from "./lib/effect"
|
||||
import { executeTool, registerToolPlugin, toolIdentity } from "./lib/tool"
|
||||
|
||||
const globToolNode = makeLocationNode({
|
||||
name: "test/glob-tool-plugin",
|
||||
layer: Layer.effectDiscard(registerToolPlugin(GlobTool.Plugin)),
|
||||
deps: [ToolRegistry.toolsNode, FSUtil.node, Ripgrep.node, Location.node, PermissionV2.node],
|
||||
})
|
||||
const grepToolNode = makeLocationNode({
|
||||
name: "test/grep-tool-plugin",
|
||||
layer: Layer.effectDiscard(registerToolPlugin(GrepTool.Plugin)),
|
||||
deps: [ToolRegistry.toolsNode, FSUtil.node, Ripgrep.node, Location.node, PermissionV2.node],
|
||||
})
|
||||
const permission = Layer.succeed(
|
||||
PermissionV2.Service,
|
||||
PermissionV2.Service.of({
|
||||
assert: () => Effect.void,
|
||||
ask: () => Effect.die("unused"),
|
||||
reply: () => Effect.die("unused"),
|
||||
get: () => Effect.die("unused"),
|
||||
forSession: () => Effect.die("unused"),
|
||||
list: () => Effect.die("unused"),
|
||||
}),
|
||||
)
|
||||
const sessionID = SessionV2.ID.make("ses_search_tool_test")
|
||||
|
||||
const withTools = <A, E, R>(directory: string, body: (registry: ToolRegistry.Interface) => Effect.Effect<A, E, R>) =>
|
||||
Effect.gen(function* () {
|
||||
return yield* body(yield* ToolRegistry.Service)
|
||||
}).pipe(
|
||||
Effect.provide(
|
||||
AppNodeBuilder.build(LayerNode.group([ToolRegistry.node, ToolRegistry.toolsNode, globToolNode, grepToolNode]), [
|
||||
[
|
||||
Location.node,
|
||||
Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
|
||||
],
|
||||
[PermissionV2.node, permission],
|
||||
[ToolOutputStore.node, ToolOutputStore.nodeWithoutConfig],
|
||||
]),
|
||||
),
|
||||
)
|
||||
|
||||
const call = (name: "glob" | "grep", input: unknown) => ({
|
||||
sessionID,
|
||||
...toolIdentity,
|
||||
call: { type: "tool-call" as const, id: `call-${name}`, name, input },
|
||||
})
|
||||
|
||||
const it = testEffect(Layer.empty)
|
||||
|
||||
describe("search tools", () => {
|
||||
for (const name of ["glob", "grep"] as const) {
|
||||
it.live(`${name} reports a missing search path`, () =>
|
||||
Effect.acquireUseRelease(
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) =>
|
||||
withTools(tmp.path, (registry) =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* executeTool(
|
||||
registry,
|
||||
call(name, { path: "missing", pattern: name === "glob" ? "*" : "needle" }),
|
||||
)
|
||||
expect(result).toEqual({ type: "error", value: "Search path does not exist: missing" })
|
||||
}),
|
||||
),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -47,7 +47,15 @@ const permission = Layer.succeed(
|
||||
Effect.sync(() => assertions.push(input)).pipe(
|
||||
Effect.andThen(Effect.suspend(() => afterPermission(input))),
|
||||
Effect.andThen(
|
||||
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
|
||||
input.action === denyAction
|
||||
? Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
@@ -75,7 +83,6 @@ const executionNode = makeGlobalNode({
|
||||
const session = yield* store.get(id)
|
||||
if (!session) return
|
||||
const assistantMessageID = SessionMessage.ID.create()
|
||||
const textID = "text_shell_test"
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID: id,
|
||||
assistantMessageID,
|
||||
@@ -85,12 +92,12 @@ const executionNode = makeGlobalNode({
|
||||
yield* events.publish(SessionEvent.Text.Started, {
|
||||
sessionID: id,
|
||||
assistantMessageID,
|
||||
textID,
|
||||
ordinal: 0,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID: id,
|
||||
assistantMessageID,
|
||||
textID,
|
||||
ordinal: 0,
|
||||
text: "ok",
|
||||
})
|
||||
yield* events.publish(SessionEvent.Step.Ended, {
|
||||
@@ -171,9 +178,11 @@ const withSession = <A, E, R>(directory: string, body: (registry: ToolRegistry.I
|
||||
})
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const locationLayer = locations.get(location)
|
||||
const registry = yield* ToolRegistry.Service.pipe(Effect.provide(locationLayer))
|
||||
yield* waitForTool(registry, ShellTool.name)
|
||||
return yield* body(registry).pipe(Effect.provide(locationLayer))
|
||||
return yield* Effect.gen(function* () {
|
||||
const registry = yield* ToolRegistry.Service
|
||||
yield* waitForTool(registry, ShellTool.name)
|
||||
return yield* body(registry)
|
||||
}).pipe(Effect.provide(locationLayer), Effect.ensuring(locations.invalidate(location)))
|
||||
})
|
||||
|
||||
describe("ShellTool", () => {
|
||||
@@ -478,9 +487,13 @@ describe("ShellTool", () => {
|
||||
const structured = settled.output?.structured as Record<string, unknown> | undefined
|
||||
const shellID = typeof structured?.shellID === "string" ? structured.shellID : undefined
|
||||
expect(settled.output?.structured).toMatchObject({ truncated: false })
|
||||
expect(settled.output?.content[0]).toMatchObject({
|
||||
expect(settled.output?.content[0]).toEqual({
|
||||
type: "text",
|
||||
text: expect.stringContaining("running in the background"),
|
||||
text: "The command was moved to the background.",
|
||||
})
|
||||
expect(settled.output?.content[1]).toMatchObject({
|
||||
type: "text",
|
||||
text: expect.stringContaining("DO NOT sleep, poll"),
|
||||
})
|
||||
expect(shellID).toStartWith("sh_")
|
||||
|
||||
|
||||
@@ -55,7 +55,17 @@ describe("SkillTool", () => {
|
||||
PermissionV2.Service.of({
|
||||
assert: (input) =>
|
||||
Effect.sync(() => assertions.push(input)).pipe(
|
||||
Effect.andThen(deny ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void),
|
||||
Effect.andThen(
|
||||
deny
|
||||
? Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
reply: () => Effect.die("unused"),
|
||||
|
||||
@@ -49,7 +49,6 @@ const executionNode = makeGlobalNode({
|
||||
}
|
||||
completed.add(sessionID)
|
||||
const assistantMessageID = SessionMessage.ID.create()
|
||||
const textID = "text_subagent_test"
|
||||
yield* events.publish(SessionEvent.Step.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
@@ -59,12 +58,12 @@ const executionNode = makeGlobalNode({
|
||||
yield* events.publish(SessionEvent.Text.Started, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
textID,
|
||||
ordinal: 0,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Text.Ended, {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
textID,
|
||||
ordinal: 0,
|
||||
text: childText,
|
||||
})
|
||||
yield* events.publish(SessionEvent.Step.Ended, {
|
||||
|
||||
@@ -33,7 +33,17 @@ const permission = Layer.succeed(
|
||||
PermissionV2.Service.of({
|
||||
assert: (input) =>
|
||||
Effect.sync(() => assertions.push(input)).pipe(
|
||||
Effect.andThen(deny ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void),
|
||||
Effect.andThen(
|
||||
deny
|
||||
? Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
reply: () => Effect.die("unused"),
|
||||
|
||||
@@ -38,7 +38,15 @@ const permission = Layer.succeed(
|
||||
assert: (input) =>
|
||||
Effect.sync(() => assertions.push(input)).pipe(
|
||||
Effect.andThen(
|
||||
input.action === denyAction ? Effect.fail(new PermissionV2.DeniedError({ rules: [] })) : Effect.void,
|
||||
input.action === denyAction
|
||||
? Effect.fail(
|
||||
new PermissionV2.DeniedError({
|
||||
rules: [],
|
||||
permission: input.action,
|
||||
resources: input.resources,
|
||||
}),
|
||||
)
|
||||
: Effect.void,
|
||||
),
|
||||
),
|
||||
ask: () => Effect.die("unused"),
|
||||
|
||||
@@ -619,6 +619,11 @@ const onOutputTextDelta = (state: ParserState, event: OpenAIResponsesEvent): Ste
|
||||
]
|
||||
}
|
||||
|
||||
const onOutputTextDone = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||
const events: LLMEvent[] = []
|
||||
return [{ ...state, lifecycle: Lifecycle.textEnd(state.lifecycle, events, event.item_id ?? "text-0") }, events]
|
||||
}
|
||||
|
||||
const onReasoningDelta = (state: ParserState, event: OpenAIResponsesEvent): StepResult => {
|
||||
if (!event.delta) return [state, NO_EVENTS]
|
||||
const events: LLMEvent[] = []
|
||||
@@ -810,6 +815,8 @@ const onOutputItemDone = Effect.fn("OpenAIResponses.onOutputItemDone")(function*
|
||||
const item = event.item
|
||||
if (!item) return [state, NO_EVENTS] satisfies StepResult
|
||||
|
||||
if (item.type === "message" && item.id) return onOutputTextDone(state, { ...event, item_id: item.id })
|
||||
|
||||
if (item.type === "function_call") {
|
||||
if (!item.id || !item.call_id || !item.name) return [state, NO_EVENTS] satisfies StepResult
|
||||
const tools = state.tools[item.id]
|
||||
@@ -920,6 +927,7 @@ const onError = (state: ParserState, event: OpenAIResponsesEvent): StepResult =>
|
||||
|
||||
const step = (state: ParserState, event: OpenAIResponsesEvent) => {
|
||||
if (event.type === "response.output_text.delta") return Effect.succeed(onOutputTextDelta(state, event))
|
||||
if (event.type === "response.output_text.done") return Effect.succeed(onOutputTextDone(state, event))
|
||||
if (
|
||||
event.type === "response.reasoning_text.delta" ||
|
||||
event.type === "response.reasoning_summary.delta" ||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Schema } from "effect"
|
||||
import { ProviderMetadata } from "@opencode-ai/schema/llm"
|
||||
import { LLM, ProviderMetadata } from "@opencode-ai/schema/llm"
|
||||
|
||||
export { ProviderMetadata }
|
||||
|
||||
@@ -36,7 +36,7 @@ export type TextVerbosity = Schema.Schema.Type<typeof TextVerbosity>
|
||||
export const MessageRole = Schema.Literals(["system", "user", "assistant", "tool"])
|
||||
export type MessageRole = Schema.Schema.Type<typeof MessageRole>
|
||||
|
||||
export const FinishReason = Schema.Literals(["stop", "length", "tool-calls", "content-filter", "error", "unknown"])
|
||||
export const FinishReason = LLM.FinishReason
|
||||
export type FinishReason = Schema.Schema.Type<typeof FinishReason>
|
||||
|
||||
export const JsonSchema = Schema.Record(Schema.String, Schema.Unknown)
|
||||
|
||||
@@ -764,6 +764,35 @@ describe("OpenAI Responses route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
// OpenAI's documented stream orders output text within one message item; no
|
||||
// provider-valid same-kind overlap is evidenced, so done boundaries close it.
|
||||
it.effect("closes sequential output messages before starting the next", () =>
|
||||
Effect.gen(function* () {
|
||||
const response = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents(
|
||||
{ type: "response.output_text.delta", item_id: "msg_1", delta: "First" },
|
||||
{ type: "response.output_text.done", item_id: "msg_1" },
|
||||
{ type: "response.output_text.delta", item_id: "msg_2", delta: "Second" },
|
||||
{ type: "response.output_item.done", item: { type: "message", id: "msg_2" } },
|
||||
{ type: "response.completed", response: { id: "resp_1" } },
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
expect(response.events.filter((event) => event.type.startsWith("text-"))).toEqual([
|
||||
{ type: "text-start", id: "msg_1" },
|
||||
{ type: "text-delta", id: "msg_1", text: "First" },
|
||||
{ type: "text-end", id: "msg_1" },
|
||||
{ type: "text-start", id: "msg_2" },
|
||||
{ type: "text-delta", id: "msg_2", text: "Second" },
|
||||
{ type: "text-end", id: "msg_2" },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("parses reasoning summary stream fixtures", () =>
|
||||
Effect.gen(function* () {
|
||||
const body = sseEvents(
|
||||
|
||||
@@ -165,8 +165,8 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
}
|
||||
}
|
||||
if (
|
||||
event.type === "session.execution.settled" &&
|
||||
event.data.outcome === "interrupted" &&
|
||||
event.type === "session.execution.interrupted" &&
|
||||
event.data.reason === "user" &&
|
||||
(interrupted || permissionRejected || questionRejected || formCancelled)
|
||||
) {
|
||||
return
|
||||
@@ -190,11 +190,12 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
}
|
||||
|
||||
if (event.type === "session.text.started") {
|
||||
starts.set(event.data.textID, { id: partID(event.id), timestamp: time })
|
||||
starts.set("text", { id: partID(event.id), timestamp: time })
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.text.ended") {
|
||||
const started = starts.get(event.data.textID)
|
||||
const started = starts.get("text")
|
||||
starts.delete("text")
|
||||
const part: TextPart = {
|
||||
id: started?.id ?? partID(event.id),
|
||||
sessionID: input.sessionID,
|
||||
@@ -208,18 +209,19 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
}
|
||||
|
||||
if (event.type === "session.reasoning.started") {
|
||||
starts.set(event.data.reasoningID, { id: partID(event.id), timestamp: time })
|
||||
starts.set("reasoning", { id: partID(event.id), timestamp: time })
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.reasoning.ended" && input.thinking) {
|
||||
const started = starts.get(event.data.reasoningID)
|
||||
const started = starts.get("reasoning")
|
||||
starts.delete("reasoning")
|
||||
const part: ReasoningPart = {
|
||||
id: started?.id ?? partID(event.id),
|
||||
sessionID: input.sessionID,
|
||||
messageID: event.data.assistantMessageID,
|
||||
type: "reasoning",
|
||||
text: event.data.text,
|
||||
metadata: event.data.providerMetadata,
|
||||
metadata: event.data.state,
|
||||
time: { start: started?.timestamp ?? time, end: time },
|
||||
}
|
||||
if (emit("reasoning", time, { part })) continue
|
||||
@@ -257,10 +259,10 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
id: current?.id ?? partID(event.id),
|
||||
timestamp: current?.timestamp ?? time,
|
||||
assistantMessageID: event.data.assistantMessageID,
|
||||
tool: event.data.tool,
|
||||
tool: current?.tool ?? "tool",
|
||||
input: event.data.input,
|
||||
raw: current?.raw,
|
||||
provider: event.data.provider,
|
||||
provider: { executed: event.data.executed, state: event.data.state },
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -287,7 +289,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
outputPaths: event.data.outputPaths,
|
||||
result: event.data.result,
|
||||
providerCall: current.provider,
|
||||
providerResult: event.data.provider,
|
||||
providerResult: { executed: event.data.executed, state: event.data.resultState },
|
||||
rawInput: current.raw,
|
||||
},
|
||||
time: { start: current.timestamp, end: time },
|
||||
@@ -314,7 +316,7 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
metadata: {
|
||||
result: event.data.result,
|
||||
providerCall: current.provider,
|
||||
providerResult: event.data.provider,
|
||||
providerResult: { executed: event.data.executed, state: event.data.resultState },
|
||||
rawInput: current.raw,
|
||||
},
|
||||
time: { start: current.timestamp, end: time },
|
||||
@@ -349,16 +351,25 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
|
||||
continue
|
||||
}
|
||||
if (event.type === "session.execution.settled") {
|
||||
if (event.data.outcome === "failure" && !emittedError && !questionRejected && !formCancelled) {
|
||||
if (event.type === "session.execution.failed") {
|
||||
if (!emittedError && !questionRejected && !formCancelled) {
|
||||
emittedError = true
|
||||
process.exitCode = 1
|
||||
const error = event.data.error ?? { type: "unknown", message: "Session execution failed" }
|
||||
if (!emit("error", time, { error })) UI.error(error.message)
|
||||
if (!emit("error", time, { error: event.data.error })) UI.error(event.data.error.message)
|
||||
}
|
||||
if (event.data.outcome === "interrupted" && interrupted) process.exitCode = 130
|
||||
return
|
||||
}
|
||||
if (event.type === "session.execution.interrupted") {
|
||||
if (event.data.reason === "user" && interrupted) process.exitCode = 130
|
||||
if (event.data.reason !== "user" && !emittedError) {
|
||||
emittedError = true
|
||||
process.exitCode = 1
|
||||
const error = { type: "aborted" as const, message: `Session interrupted: ${event.data.reason}` }
|
||||
if (!emit("error", time, { error })) UI.error(error.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (event.type === "session.execution.succeeded") return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,54 +38,60 @@ export function outputText(content: ReadonlyArray<{ type: string; text?: string
|
||||
export function legacyTool(input: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
callID: string
|
||||
name: string
|
||||
state: SessionMessageAssistantTool["state"]
|
||||
time: SessionMessageAssistantTool["time"]
|
||||
provider?: SessionMessageAssistantTool["provider"]
|
||||
tool: SessionMessageAssistantTool
|
||||
}): ToolPart {
|
||||
const tool = input.tool
|
||||
const providerCall =
|
||||
tool.executed === undefined && tool.providerState === undefined
|
||||
? undefined
|
||||
: { executed: tool.executed, state: tool.providerState }
|
||||
const providerResult =
|
||||
tool.executed === undefined && tool.providerResultState === undefined
|
||||
? undefined
|
||||
: { executed: tool.executed, state: tool.providerResultState }
|
||||
const base = {
|
||||
id: `prt_${input.callID}`,
|
||||
id: `prt_${tool.id}`,
|
||||
sessionID: input.sessionID,
|
||||
messageID: input.messageID,
|
||||
type: "tool" as const,
|
||||
callID: input.callID,
|
||||
tool: input.name,
|
||||
callID: tool.id,
|
||||
tool: tool.name,
|
||||
}
|
||||
if (input.state.status === "pending") {
|
||||
if (tool.state.status === "pending") {
|
||||
return {
|
||||
...base,
|
||||
state: { status: "pending", input: {}, raw: input.state.input },
|
||||
state: { status: "pending", input: {}, raw: tool.state.input },
|
||||
}
|
||||
}
|
||||
if (input.state.status === "running") {
|
||||
if (tool.state.status === "running") {
|
||||
return {
|
||||
...base,
|
||||
state: {
|
||||
status: "running",
|
||||
input: input.state.input,
|
||||
title: input.name,
|
||||
metadata: { structured: input.state.structured, content: input.state.content, providerCall: input.provider },
|
||||
time: { start: input.time.ran ?? input.time.created },
|
||||
input: tool.state.input,
|
||||
title: tool.name,
|
||||
metadata: { structured: tool.state.structured, content: tool.state.content, providerCall },
|
||||
time: { start: tool.time.ran ?? tool.time.created },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (input.state.status === "completed") {
|
||||
if (tool.state.status === "completed") {
|
||||
return {
|
||||
...base,
|
||||
state: {
|
||||
status: "completed",
|
||||
input: input.state.input,
|
||||
output: outputText(input.state.content),
|
||||
title: input.name,
|
||||
input: tool.state.input,
|
||||
output: outputText(tool.state.content),
|
||||
title: tool.name,
|
||||
metadata: {
|
||||
structured: input.state.structured,
|
||||
content: input.state.content,
|
||||
outputPaths: input.state.outputPaths,
|
||||
result: input.state.result,
|
||||
providerCall: input.provider,
|
||||
structured: tool.state.structured,
|
||||
content: tool.state.content,
|
||||
outputPaths: tool.state.outputPaths,
|
||||
result: tool.state.result,
|
||||
providerCall,
|
||||
providerResult,
|
||||
},
|
||||
time: { start: input.time.ran ?? input.time.created, end: input.time.completed ?? input.time.created },
|
||||
time: { start: tool.time.ran ?? tool.time.created, end: tool.time.completed ?? tool.time.created },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -93,15 +99,16 @@ export function legacyTool(input: {
|
||||
...base,
|
||||
state: {
|
||||
status: "error",
|
||||
input: input.state.input,
|
||||
error: input.state.error.message,
|
||||
input: tool.state.input,
|
||||
error: tool.state.error.message,
|
||||
metadata: {
|
||||
structured: input.state.structured,
|
||||
content: input.state.content,
|
||||
result: input.state.result,
|
||||
providerCall: input.provider,
|
||||
structured: tool.state.structured,
|
||||
content: tool.state.content,
|
||||
result: tool.state.result,
|
||||
providerCall,
|
||||
providerResult,
|
||||
},
|
||||
time: { start: input.time.ran ?? input.time.created, end: input.time.completed ?? input.time.created },
|
||||
time: { start: tool.time.ran ?? tool.time.created, end: tool.time.completed ?? tool.time.created },
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -141,6 +148,7 @@ type ToolTrack = {
|
||||
name: string
|
||||
input: Record<string, unknown>
|
||||
started: number
|
||||
providerState?: Record<string, unknown>
|
||||
}
|
||||
|
||||
type ChildState = {
|
||||
@@ -225,6 +233,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
const pendingEvents = new Map<string, V2Event[]>()
|
||||
const hydrations = new Map<string, Promise<void>>()
|
||||
let selected: string | undefined
|
||||
const fragmentKey = (messageID: string, partID: string) => `${messageID}\u0000${partID}`
|
||||
|
||||
const ensureChild = (sessionID: string): ChildState => {
|
||||
const existing = children.get(sessionID)
|
||||
@@ -304,11 +313,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
const part = legacyTool({
|
||||
sessionID: child.sessionID,
|
||||
messageID,
|
||||
callID: item.id,
|
||||
name: item.name,
|
||||
state: item.state,
|
||||
time: item.time,
|
||||
provider: item.provider,
|
||||
tool: item,
|
||||
})
|
||||
if (item.state.status === "pending") return
|
||||
child.callIDs.add(item.id)
|
||||
@@ -337,31 +342,37 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
}
|
||||
if (message.type !== "assistant") continue
|
||||
child.messageIDs.add(message.id)
|
||||
let textOrdinal = 0
|
||||
let reasoningOrdinal = 0
|
||||
for (const item of message.content) {
|
||||
if (item.type === "text") {
|
||||
child.text.set(item.id, item.text)
|
||||
child.projectedText.set(item.id, item.text)
|
||||
setFrame(child, `text:${item.id}`, {
|
||||
const id = `text:${textOrdinal++}`
|
||||
const key = fragmentKey(message.id, id)
|
||||
child.text.set(key, item.text)
|
||||
child.projectedText.set(key, item.text)
|
||||
setFrame(child, key, {
|
||||
kind: "assistant",
|
||||
source: "assistant",
|
||||
text: item.text,
|
||||
phase: "progress",
|
||||
messageID: message.id,
|
||||
partID: item.id,
|
||||
partID: id,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (item.type === "reasoning") {
|
||||
child.reasoning.set(item.id, item.text)
|
||||
child.projectedReasoning.set(item.id, item.text)
|
||||
const id = `reasoning:${reasoningOrdinal++}`
|
||||
const key = fragmentKey(message.id, id)
|
||||
child.reasoning.set(key, item.text)
|
||||
child.projectedReasoning.set(key, item.text)
|
||||
if (input.thinking)
|
||||
setFrame(child, `reasoning:${item.id}`, {
|
||||
setFrame(child, key, {
|
||||
kind: "reasoning",
|
||||
source: "reasoning",
|
||||
text: `Thinking: ${item.text}`,
|
||||
phase: "progress",
|
||||
messageID: message.id,
|
||||
partID: item.id,
|
||||
partID: id,
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -438,74 +449,88 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
input.emit()
|
||||
return
|
||||
}
|
||||
if (event.type === "session.text.started") {
|
||||
return
|
||||
}
|
||||
if (event.type === "session.text.delta") {
|
||||
const projected = child.projectedText.get(event.data.textID)
|
||||
const id = `text:${event.data.ordinal}`
|
||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
||||
const projected = child.projectedText.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
child.projectedText.set(event.data.textID, projected.slice(covered + event.data.delta.length))
|
||||
child.projectedText.set(key, projected.slice(covered + event.data.delta.length))
|
||||
return
|
||||
}
|
||||
const next = (child.text.get(event.data.textID) ?? "") + event.data.delta
|
||||
child.text.set(event.data.textID, next)
|
||||
setFrame(child, `text:${event.data.textID}`, {
|
||||
const next = (child.text.get(key) ?? "") + event.data.delta
|
||||
child.text.set(key, next)
|
||||
setFrame(child, key, {
|
||||
kind: "assistant",
|
||||
source: "assistant",
|
||||
text: next,
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.textID,
|
||||
partID: id,
|
||||
})
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.text.ended") {
|
||||
child.text.set(event.data.textID, event.data.text)
|
||||
child.projectedText.delete(event.data.textID)
|
||||
setFrame(child, `text:${event.data.textID}`, {
|
||||
const id = `text:${event.data.ordinal}`
|
||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
||||
child.text.set(key, event.data.text)
|
||||
child.projectedText.delete(key)
|
||||
setFrame(child, key, {
|
||||
kind: "assistant",
|
||||
source: "assistant",
|
||||
text: event.data.text,
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.textID,
|
||||
partID: id,
|
||||
})
|
||||
touch(child, event.created)
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.reasoning.started") {
|
||||
return
|
||||
}
|
||||
if (event.type === "session.reasoning.delta") {
|
||||
const projected = child.projectedReasoning.get(event.data.reasoningID)
|
||||
const id = `reasoning:${event.data.ordinal}`
|
||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
||||
const projected = child.projectedReasoning.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
child.projectedReasoning.set(event.data.reasoningID, projected.slice(covered + event.data.delta.length))
|
||||
child.projectedReasoning.set(key, projected.slice(covered + event.data.delta.length))
|
||||
return
|
||||
}
|
||||
const next = (child.reasoning.get(event.data.reasoningID) ?? "") + event.data.delta
|
||||
child.reasoning.set(event.data.reasoningID, next)
|
||||
const next = (child.reasoning.get(key) ?? "") + event.data.delta
|
||||
child.reasoning.set(key, next)
|
||||
if (!input.thinking) return
|
||||
setFrame(child, `reasoning:${event.data.reasoningID}`, {
|
||||
setFrame(child, key, {
|
||||
kind: "reasoning",
|
||||
source: "reasoning",
|
||||
text: `Thinking: ${next}`,
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.reasoningID,
|
||||
partID: id,
|
||||
})
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.reasoning.ended") {
|
||||
child.reasoning.set(event.data.reasoningID, event.data.text)
|
||||
child.projectedReasoning.delete(event.data.reasoningID)
|
||||
const id = `reasoning:${event.data.ordinal}`
|
||||
const key = fragmentKey(event.data.assistantMessageID, id)
|
||||
child.reasoning.set(key, event.data.text)
|
||||
child.projectedReasoning.delete(key)
|
||||
if (!input.thinking) return
|
||||
setFrame(child, `reasoning:${event.data.reasoningID}`, {
|
||||
setFrame(child, key, {
|
||||
kind: "reasoning",
|
||||
source: "reasoning",
|
||||
text: `Thinking: ${event.data.text}`,
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.reasoningID,
|
||||
partID: id,
|
||||
})
|
||||
notifyDetail(child)
|
||||
return
|
||||
@@ -517,17 +542,19 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
if (event.type === "session.tool.called") {
|
||||
const current = child.tools.get(event.data.callID)
|
||||
child.tools.set(event.data.callID, {
|
||||
name: event.data.tool,
|
||||
name: current?.name ?? "tool",
|
||||
input: event.data.input,
|
||||
started: current?.started ?? event.created,
|
||||
providerState: event.data.state,
|
||||
})
|
||||
childTool(
|
||||
child,
|
||||
{
|
||||
type: "tool",
|
||||
id: event.data.callID,
|
||||
name: event.data.tool,
|
||||
provider: event.data.provider,
|
||||
name: current?.name ?? "tool",
|
||||
executed: event.data.executed,
|
||||
providerState: event.data.state,
|
||||
state: { status: "running", input: event.data.input, structured: {}, content: [] },
|
||||
time: { created: current?.started ?? event.created, ran: event.created },
|
||||
},
|
||||
@@ -547,7 +574,9 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
type: "tool",
|
||||
id: event.data.callID,
|
||||
name: current?.name ?? "tool",
|
||||
provider: event.data.provider,
|
||||
executed: event.data.executed,
|
||||
providerState: current?.providerState,
|
||||
providerResultState: event.data.resultState,
|
||||
state: failed
|
||||
? {
|
||||
status: "error",
|
||||
@@ -577,6 +606,7 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.step.ended") return
|
||||
if (event.type === "session.step.failed") {
|
||||
setFrame(child, `error:step:${event.data.assistantMessageID}`, {
|
||||
kind: "error",
|
||||
@@ -589,9 +619,23 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
notifyDetail(child)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.execution.settled") {
|
||||
if (event.type === "session.execution.started") {
|
||||
child.status = "running"
|
||||
touch(child, event.created)
|
||||
input.emit()
|
||||
return
|
||||
}
|
||||
if (
|
||||
event.type === "session.execution.succeeded" ||
|
||||
event.type === "session.execution.failed" ||
|
||||
event.type === "session.execution.interrupted"
|
||||
) {
|
||||
child.status =
|
||||
event.data.outcome === "success" ? "completed" : event.data.outcome === "interrupted" ? "cancelled" : "error"
|
||||
event.type === "session.execution.succeeded"
|
||||
? "completed"
|
||||
: event.type === "session.execution.interrupted"
|
||||
? "cancelled"
|
||||
: "error"
|
||||
touch(child, event.created)
|
||||
input.emit()
|
||||
}
|
||||
@@ -613,8 +657,12 @@ export function createSubagentTracker(input: SubagentTrackerInput): SubagentTrac
|
||||
|
||||
return {
|
||||
main(event) {
|
||||
if (event.type === "session.tool.input.started") {
|
||||
if (event.data.name === "subagent") pendingCalls.set(event.data.callID, {})
|
||||
return
|
||||
}
|
||||
if (event.type === "session.tool.called") {
|
||||
if (event.data.tool === "subagent") pendingCalls.set(event.data.callID, event.data.input)
|
||||
if (pendingCalls.has(event.data.callID)) pendingCalls.set(event.data.callID, event.data.input)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.tool.failed") {
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
QuestionRequest,
|
||||
QuestionV2Request,
|
||||
SessionMessage,
|
||||
SessionMessageAssistant,
|
||||
SessionMessageAssistantTool,
|
||||
V2Event,
|
||||
} from "@opencode-ai/sdk/v2"
|
||||
@@ -95,6 +94,7 @@ type ToolState = {
|
||||
input: Record<string, unknown>
|
||||
started: number
|
||||
running: boolean
|
||||
providerState?: Record<string, unknown>
|
||||
}
|
||||
|
||||
type State = {
|
||||
@@ -352,11 +352,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
const part = legacyTool({
|
||||
sessionID: input.sessionID,
|
||||
messageID,
|
||||
callID: item.id,
|
||||
name: item.name,
|
||||
state: item.state,
|
||||
time: item.time,
|
||||
provider: item.provider,
|
||||
tool: item,
|
||||
})
|
||||
if (item.state.status === "pending") return
|
||||
if (item.state.status === "running") {
|
||||
@@ -367,6 +363,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
input: item.state.input,
|
||||
started: item.time.ran ?? item.time.created,
|
||||
running: true,
|
||||
providerState: item.providerState,
|
||||
})
|
||||
write([toolCommit(part, "start")], { phase: "running", status: `running ${item.name}` })
|
||||
return
|
||||
@@ -443,9 +440,12 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
}
|
||||
if (message.type !== "assistant") return
|
||||
state.messageIDs.add(message.id)
|
||||
let textOrdinal = 0
|
||||
let reasoningOrdinal = 0
|
||||
for (const item of message.content) {
|
||||
if (item.type === "text") {
|
||||
const key = streamPartKey(message.id, item.id)
|
||||
const id = `text:${textOrdinal++}`
|
||||
const key = streamPartKey(message.id, id)
|
||||
const sent = state.text.get(key)?.length ?? 0
|
||||
state.text.set(key, item.text)
|
||||
if (render) state.projectedText.set(key, item.text)
|
||||
@@ -457,13 +457,14 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
text: item.text.slice(sent),
|
||||
phase: "progress",
|
||||
messageID: message.id,
|
||||
partID: item.id,
|
||||
partID: id,
|
||||
},
|
||||
])
|
||||
continue
|
||||
}
|
||||
if (item.type === "reasoning") {
|
||||
const key = streamPartKey(message.id, item.id)
|
||||
const id = `reasoning:${reasoningOrdinal++}`
|
||||
const key = streamPartKey(message.id, id)
|
||||
const sent = state.reasoning.get(key)?.length ?? 0
|
||||
state.reasoning.set(key, item.text)
|
||||
if (render) state.projectedReasoning.set(key, item.text)
|
||||
@@ -475,7 +476,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
text: sent === 0 ? `Thinking: ${item.text}` : item.text.slice(sent),
|
||||
phase: "progress",
|
||||
messageID: message.id,
|
||||
partID: item.id,
|
||||
partID: id,
|
||||
},
|
||||
])
|
||||
continue
|
||||
@@ -596,8 +597,12 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
if (owned) wait.resolve()
|
||||
return
|
||||
}
|
||||
if (event.type === "session.text.started") {
|
||||
return
|
||||
}
|
||||
if (event.type === "session.text.delta") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
|
||||
const id = `text:${event.data.ordinal}`
|
||||
const key = streamPartKey(event.data.assistantMessageID, id)
|
||||
const projected = state.projectedText.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
@@ -613,13 +618,14 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
text: event.data.delta,
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.textID,
|
||||
partID: id,
|
||||
},
|
||||
])
|
||||
return
|
||||
}
|
||||
if (event.type === "session.text.ended") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.textID)
|
||||
const id = `text:${event.data.ordinal}`
|
||||
const key = streamPartKey(event.data.assistantMessageID, id)
|
||||
const previous = state.text.get(key) ?? ""
|
||||
if (event.data.text.length > previous.length)
|
||||
write([
|
||||
@@ -629,15 +635,19 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
text: event.data.text.slice(previous.length),
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.textID,
|
||||
partID: id,
|
||||
},
|
||||
])
|
||||
state.text.set(key, event.data.text)
|
||||
state.projectedText.delete(key)
|
||||
return
|
||||
}
|
||||
if (event.type === "session.reasoning.started") {
|
||||
return
|
||||
}
|
||||
if (event.type === "session.reasoning.delta") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
|
||||
const id = `reasoning:${event.data.ordinal}`
|
||||
const key = streamPartKey(event.data.assistantMessageID, id)
|
||||
const projected = state.projectedReasoning.get(key)
|
||||
const covered = projected?.indexOf(event.data.delta) ?? -1
|
||||
if (projected && covered >= 0) {
|
||||
@@ -654,13 +664,14 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
text: previous ? event.data.delta : `Thinking: ${event.data.delta}`,
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.reasoningID,
|
||||
partID: id,
|
||||
},
|
||||
])
|
||||
return
|
||||
}
|
||||
if (event.type === "session.reasoning.ended") {
|
||||
const key = streamPartKey(event.data.assistantMessageID, event.data.reasoningID)
|
||||
const id = `reasoning:${event.data.ordinal}`
|
||||
const key = streamPartKey(event.data.assistantMessageID, id)
|
||||
const previous = state.reasoning.get(key) ?? ""
|
||||
if (input.thinking && event.data.text.length > previous.length)
|
||||
write([
|
||||
@@ -670,7 +681,7 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
text: previous ? event.data.text.slice(previous.length) : `Thinking: ${event.data.text}`,
|
||||
phase: "progress",
|
||||
messageID: event.data.assistantMessageID,
|
||||
partID: event.data.reasoningID,
|
||||
partID: id,
|
||||
},
|
||||
])
|
||||
state.reasoning.set(key, event.data.text)
|
||||
@@ -693,8 +704,9 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
const item: SessionMessageAssistantTool = {
|
||||
type: "tool",
|
||||
id: event.data.callID,
|
||||
name: event.data.tool,
|
||||
provider: event.data.provider,
|
||||
name: current?.name ?? "tool",
|
||||
executed: event.data.executed,
|
||||
providerState: event.data.state,
|
||||
state: { status: "running", input: event.data.input, structured: {}, content: [] },
|
||||
time: { created: current?.started ?? event.created, ran: event.created },
|
||||
}
|
||||
@@ -709,7 +721,9 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
type: "tool",
|
||||
id: event.data.callID,
|
||||
name: current?.name ?? "tool",
|
||||
provider: event.data.provider,
|
||||
executed: event.data.executed,
|
||||
providerState: current?.providerState,
|
||||
providerResultState: event.data.resultState,
|
||||
state: failed
|
||||
? {
|
||||
status: "error",
|
||||
@@ -761,7 +775,6 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
event.data.tokens.cache.write
|
||||
const usage = total > 0 ? total.toLocaleString() : ""
|
||||
write([], {
|
||||
phase: event.data.finish === "tool-calls" ? "running" : "idle",
|
||||
usage: event.data.cost ? `${usage} · ${money.format(event.data.cost)}` : usage,
|
||||
})
|
||||
return
|
||||
@@ -772,21 +785,33 @@ export async function createSessionTransport(input: StreamInput): Promise<Sessio
|
||||
write([{ kind: "error", source: "system", text: errorMessage(event.data.error), phase: "start" }])
|
||||
return
|
||||
}
|
||||
if (event.type === "session.execution.settled") {
|
||||
if (event.type === "session.execution.started") {
|
||||
write([], { phase: "running" })
|
||||
return
|
||||
}
|
||||
if (
|
||||
event.type === "session.execution.succeeded" ||
|
||||
event.type === "session.execution.failed" ||
|
||||
event.type === "session.execution.interrupted"
|
||||
) {
|
||||
write([], { phase: "idle", status: "" })
|
||||
const current = state.wait
|
||||
if (!current || (!current.promoted && !current.interrupted)) return
|
||||
state.wait = undefined
|
||||
if (current.interrupted) {
|
||||
if (current.interrupted && event.type === "session.execution.interrupted" && event.data.reason === "user") {
|
||||
current.resolve()
|
||||
return
|
||||
}
|
||||
if (event.data.outcome === "failure") {
|
||||
if (event.type === "session.execution.failed") {
|
||||
if (current.failureRendered) {
|
||||
current.resolve()
|
||||
return
|
||||
}
|
||||
current.reject(new Error(event.data.error ? errorMessage(event.data.error) : "Session execution failed"))
|
||||
current.reject(new Error(errorMessage(event.data.error)))
|
||||
return
|
||||
}
|
||||
if (event.type === "session.execution.interrupted") {
|
||||
current.reject(new Error(`Session interrupted: ${event.data.reason}`))
|
||||
return
|
||||
}
|
||||
current.resolve()
|
||||
|
||||
@@ -32,11 +32,20 @@ function prompted(inputID: string): V2Event {
|
||||
}
|
||||
|
||||
function settled(outcome: "success" | "interrupted" = "success"): V2Event {
|
||||
if (outcome === "interrupted")
|
||||
return {
|
||||
id: "evt_interrupted",
|
||||
created: 0,
|
||||
type: "session.execution.interrupted",
|
||||
durable: { aggregateID: "ses_1", seq: 1, version: 1 },
|
||||
data: { sessionID: "ses_1", reason: "user" },
|
||||
}
|
||||
return {
|
||||
id: "evt_settled",
|
||||
id: "evt_succeeded",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome },
|
||||
type: "session.execution.succeeded",
|
||||
durable: { aggregateID: "ses_1", seq: 1, version: 1 },
|
||||
data: { sessionID: "ses_1" },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,19 @@ function reduce(data: ReturnType<typeof createSessionData>, event: unknown, thin
|
||||
})
|
||||
}
|
||||
|
||||
function shell(status: "running" | "exited" = "running") {
|
||||
return {
|
||||
id: "sh_1",
|
||||
status,
|
||||
command: "pwd",
|
||||
cwd: "/tmp",
|
||||
shell: "/bin/sh",
|
||||
file: "/tmp/shell-output",
|
||||
metadata: {},
|
||||
time: { started: 1, completed: status === "exited" ? 2 : undefined },
|
||||
}
|
||||
}
|
||||
|
||||
function assistant(id: string, extra: Record<string, unknown> = {}) {
|
||||
return {
|
||||
type: "message.updated",
|
||||
@@ -332,9 +345,7 @@ describe("run session data", () => {
|
||||
type: "session.shell.started",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 1,
|
||||
callID: "call-1",
|
||||
command: "pwd",
|
||||
shell: shell(),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -342,10 +353,10 @@ describe("run session data", () => {
|
||||
expect.objectContaining({
|
||||
kind: "tool",
|
||||
phase: "start",
|
||||
partID: "shell:call-1",
|
||||
partID: "shell:sh_1",
|
||||
tool: "bash",
|
||||
shell: {
|
||||
callID: "call-1",
|
||||
callID: "sh_1",
|
||||
command: "pwd",
|
||||
},
|
||||
}),
|
||||
@@ -356,9 +367,8 @@ describe("run session data", () => {
|
||||
type: "session.shell.ended",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 2,
|
||||
callID: "call-1",
|
||||
output: "/tmp/demo\n",
|
||||
shell: shell("exited"),
|
||||
output: { output: "/tmp/demo\n", cursor: 10, size: 10, truncated: false },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -366,12 +376,12 @@ describe("run session data", () => {
|
||||
expect.objectContaining({
|
||||
kind: "tool",
|
||||
phase: "progress",
|
||||
partID: "shell:call-1",
|
||||
partID: "shell:sh_1",
|
||||
tool: "bash",
|
||||
text: "/tmp/demo\n",
|
||||
toolState: "completed",
|
||||
shell: {
|
||||
callID: "call-1",
|
||||
callID: "sh_1",
|
||||
command: "pwd",
|
||||
},
|
||||
}),
|
||||
@@ -383,9 +393,7 @@ describe("run session data", () => {
|
||||
type: "session.shell.started",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 1,
|
||||
callID: "call-1",
|
||||
command: "pwd",
|
||||
shell: shell(),
|
||||
},
|
||||
}).data
|
||||
|
||||
@@ -395,7 +403,7 @@ describe("run session data", () => {
|
||||
tool({
|
||||
id: "tool-1",
|
||||
messageID: "msg-1",
|
||||
callID: "call-1",
|
||||
callID: "sh_1",
|
||||
tool: "bash",
|
||||
state: {
|
||||
status: "running",
|
||||
@@ -412,9 +420,8 @@ describe("run session data", () => {
|
||||
type: "session.shell.ended",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 2,
|
||||
callID: "call-1",
|
||||
output: "/tmp/demo\n",
|
||||
shell: shell("exited"),
|
||||
output: { output: "/tmp/demo\n", cursor: 10, size: 10, truncated: false },
|
||||
},
|
||||
}).data
|
||||
|
||||
@@ -424,7 +431,7 @@ describe("run session data", () => {
|
||||
tool({
|
||||
id: "tool-1",
|
||||
messageID: "msg-1",
|
||||
callID: "call-1",
|
||||
callID: "sh_1",
|
||||
tool: "bash",
|
||||
state: {
|
||||
status: "completed",
|
||||
@@ -449,7 +456,7 @@ describe("run session data", () => {
|
||||
tool({
|
||||
id: "tool-1",
|
||||
messageID: "msg-1",
|
||||
callID: "call-1",
|
||||
callID: "sh_1",
|
||||
tool: "bash",
|
||||
state: {
|
||||
status: "running",
|
||||
@@ -466,9 +473,7 @@ describe("run session data", () => {
|
||||
type: "session.shell.started",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 1,
|
||||
callID: "call-1",
|
||||
command: "pwd",
|
||||
shell: shell(),
|
||||
},
|
||||
}).commits,
|
||||
).toEqual([])
|
||||
@@ -478,7 +483,7 @@ describe("run session data", () => {
|
||||
tool({
|
||||
id: "tool-1",
|
||||
messageID: "msg-1",
|
||||
callID: "call-1",
|
||||
callID: "sh_1",
|
||||
tool: "bash",
|
||||
state: {
|
||||
status: "completed",
|
||||
@@ -500,9 +505,8 @@ describe("run session data", () => {
|
||||
type: "session.shell.ended",
|
||||
properties: {
|
||||
sessionID: "session-1",
|
||||
timestamp: 2,
|
||||
callID: "call-1",
|
||||
output: "/tmp/demo\n",
|
||||
shell: shell("exited"),
|
||||
output: { output: "/tmp/demo\n", cursor: 10, size: 10, truncated: false },
|
||||
},
|
||||
}).commits,
|
||||
).toEqual([])
|
||||
|
||||
@@ -214,15 +214,16 @@ describe("V2 mini transport", () => {
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
textID: "txt_1",
|
||||
ordinal: 0,
|
||||
delta: "answer",
|
||||
},
|
||||
})
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
await turn
|
||||
|
||||
@@ -269,8 +270,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
})
|
||||
return ok({
|
||||
@@ -363,8 +365,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
})
|
||||
return ok({
|
||||
@@ -460,8 +463,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
})
|
||||
return ok({
|
||||
@@ -709,7 +713,7 @@ describe("V2 mini transport", () => {
|
||||
type: "assistant",
|
||||
agent: "build",
|
||||
model: { providerID: "test", id: "model" },
|
||||
content: [{ type: "text", id: "txt_1", text: "the answer" }],
|
||||
content: [{ type: "text", text: "the answer" }],
|
||||
time: { created: 2, completed: 3 },
|
||||
},
|
||||
],
|
||||
@@ -721,6 +725,17 @@ describe("V2 mini transport", () => {
|
||||
reset = resolve
|
||||
})
|
||||
const replay = transport.replayOnResize({ localRows: () => [], reset: () => resetting })
|
||||
events.push({
|
||||
id: "evt_text_started",
|
||||
created: 0,
|
||||
type: "session.text.started",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
ordinal: 0,
|
||||
},
|
||||
})
|
||||
events.push({
|
||||
id: "evt_text",
|
||||
created: 0,
|
||||
@@ -728,7 +743,7 @@ describe("V2 mini transport", () => {
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
textID: "txt_1",
|
||||
ordinal: 0,
|
||||
delta: "answer",
|
||||
},
|
||||
})
|
||||
@@ -741,7 +756,7 @@ describe("V2 mini transport", () => {
|
||||
await transport.close()
|
||||
})
|
||||
|
||||
test("scopes repeated text and reasoning ids by assistant message", async () => {
|
||||
test("scopes text and reasoning ordinals by assistant message", async () => {
|
||||
const events = feed()
|
||||
events.push(connected())
|
||||
const client = sdk({ streams: [events] })
|
||||
@@ -754,8 +769,8 @@ describe("V2 mini transport", () => {
|
||||
agent: "build",
|
||||
model: { providerID: "test", id: "model" },
|
||||
content: [
|
||||
{ type: "reasoning", id: "reasoning-0", text: "second thought" },
|
||||
{ type: "text", id: "text-0", text: "second answer" },
|
||||
{ type: "reasoning", text: "second thought" },
|
||||
{ type: "text", text: "second answer" },
|
||||
],
|
||||
time: { created: 4, completed: 5 },
|
||||
},
|
||||
@@ -765,8 +780,8 @@ describe("V2 mini transport", () => {
|
||||
agent: "build",
|
||||
model: { providerID: "test", id: "model" },
|
||||
content: [
|
||||
{ type: "reasoning", id: "reasoning-0", text: "first thought" },
|
||||
{ type: "text", id: "text-0", text: "first answer" },
|
||||
{ type: "reasoning", text: "first thought" },
|
||||
{ type: "text", text: "first answer" },
|
||||
],
|
||||
time: { created: 2, completed: 3 },
|
||||
},
|
||||
@@ -814,7 +829,7 @@ describe("V2 mini transport", () => {
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
reasoningID: "reasoning_1",
|
||||
ordinal: 0,
|
||||
text: "considering",
|
||||
},
|
||||
})
|
||||
@@ -824,6 +839,52 @@ describe("V2 mini transport", () => {
|
||||
await transport.close()
|
||||
})
|
||||
|
||||
test("renders a live tool start when the call begins", async () => {
|
||||
const events = feed()
|
||||
events.push(connected())
|
||||
const client = sdk({ streams: [events] })
|
||||
const ui = footer()
|
||||
const transport = await createSessionTransport({
|
||||
sdk: client,
|
||||
sessionID: "ses_1",
|
||||
thinking: false,
|
||||
limits: () => ({}),
|
||||
footer: ui.api,
|
||||
})
|
||||
|
||||
events.push({
|
||||
id: "evt_tool_input",
|
||||
created: 1,
|
||||
type: "session.tool.input.started",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
callID: "call_read",
|
||||
name: "read",
|
||||
},
|
||||
})
|
||||
events.push({
|
||||
id: "evt_tool_called",
|
||||
created: 2,
|
||||
type: "session.tool.called",
|
||||
durable: durable("ses_1", 1),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_assistant",
|
||||
callID: "call_read",
|
||||
input: { path: "README.md" },
|
||||
executed: false,
|
||||
},
|
||||
})
|
||||
await Bun.sleep(0)
|
||||
|
||||
expect(ui.commits).toContainEqual(
|
||||
expect.objectContaining({ kind: "tool", phase: "start", partID: "prt_call_read", tool: "read" }),
|
||||
)
|
||||
await transport.close()
|
||||
})
|
||||
|
||||
test("resolves an interrupted turn even when promotion never arrived", async () => {
|
||||
const events = feed()
|
||||
events.push(connected())
|
||||
@@ -865,8 +926,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.interrupted",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1", reason: "user" },
|
||||
})
|
||||
await turn
|
||||
|
||||
@@ -931,8 +993,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
await turn
|
||||
|
||||
@@ -993,8 +1056,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.interrupted",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1", reason: "user" },
|
||||
})
|
||||
await turn
|
||||
|
||||
@@ -1219,8 +1283,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
})
|
||||
return ok({
|
||||
@@ -1296,8 +1361,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
})
|
||||
return ok(undefined) as never
|
||||
@@ -1366,8 +1432,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_unrelated_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
await Bun.sleep(0)
|
||||
await Bun.sleep(0)
|
||||
@@ -1387,8 +1454,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_skill_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_1", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_1"),
|
||||
data: { sessionID: "ses_1" },
|
||||
})
|
||||
await turn
|
||||
|
||||
@@ -1509,7 +1577,7 @@ describe("V2 mini transport", () => {
|
||||
data: {
|
||||
sessionID: "ses_child",
|
||||
assistantMessageID: "msg_child_a",
|
||||
textID: "txt_child",
|
||||
ordinal: 0,
|
||||
delta: "child answer",
|
||||
},
|
||||
})
|
||||
@@ -1519,8 +1587,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_child_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_child", outcome: "success" },
|
||||
type: "session.execution.succeeded",
|
||||
durable: durable("ses_child"),
|
||||
data: { sessionID: "ses_child" },
|
||||
})
|
||||
while (!states().some((state) => state.tabs.some((tab) => tab.status === "completed"))) await Bun.sleep(0)
|
||||
await transport.close()
|
||||
@@ -1576,8 +1645,9 @@ describe("V2 mini transport", () => {
|
||||
events.push({
|
||||
id: "evt_child_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_child", outcome: "interrupted" },
|
||||
type: "session.execution.interrupted",
|
||||
durable: durable("ses_child"),
|
||||
data: { sessionID: "ses_child", reason: "user" },
|
||||
})
|
||||
await Bun.sleep(0)
|
||||
resolveGet?.()
|
||||
@@ -1633,6 +1703,18 @@ describe("V2 mini transport", () => {
|
||||
},
|
||||
})
|
||||
// Parent's background subagent tool.success adopts the child mid-discovery.
|
||||
events.push({
|
||||
id: "evt_parent_input",
|
||||
created: 0,
|
||||
type: "session.tool.input.started",
|
||||
durable: durable("ses_1"),
|
||||
data: {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_parent_a",
|
||||
callID: "call_sub",
|
||||
name: "subagent",
|
||||
},
|
||||
})
|
||||
events.push({
|
||||
id: "evt_parent_call",
|
||||
created: 0,
|
||||
@@ -1642,9 +1724,8 @@ describe("V2 mini transport", () => {
|
||||
sessionID: "ses_1",
|
||||
assistantMessageID: "msg_parent_a",
|
||||
callID: "call_sub",
|
||||
tool: "subagent",
|
||||
input: { agent: "explore", description: "Find things", prompt: "go", background: true },
|
||||
provider: { executed: true },
|
||||
executed: true,
|
||||
},
|
||||
})
|
||||
events.push({
|
||||
@@ -1658,15 +1739,16 @@ describe("V2 mini transport", () => {
|
||||
callID: "call_sub",
|
||||
structured: { sessionID: "ses_child", status: "running", output: "" },
|
||||
content: [],
|
||||
provider: { executed: true },
|
||||
executed: true,
|
||||
},
|
||||
})
|
||||
// The settled event arrives after adoption, so it applies directly.
|
||||
events.push({
|
||||
id: "evt_child_settled",
|
||||
created: 0,
|
||||
type: "session.execution.settled",
|
||||
data: { sessionID: "ses_child", outcome: "interrupted" },
|
||||
type: "session.execution.interrupted",
|
||||
durable: durable("ses_child"),
|
||||
data: { sessionID: "ses_child", reason: "shutdown" },
|
||||
})
|
||||
while (!states().some((state) => state.tabs.some((tab) => tab.status === "cancelled"))) await Bun.sleep(0)
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ test.skip("text ended populates assistant text content", () => {
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
textID: "text-1",
|
||||
ordinal: 0,
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
)
|
||||
@@ -115,7 +115,7 @@ test.skip("text ended populates assistant text content", () => {
|
||||
data: {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
textID: "text-1",
|
||||
ordinal: 0,
|
||||
text: "hello assistant",
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
@@ -123,7 +123,7 @@ test.skip("text ended populates assistant text content", () => {
|
||||
|
||||
expect(state.messages[0]?.type).toBe("assistant")
|
||||
if (state.messages[0]?.type !== "assistant") return
|
||||
expect(state.messages[0].content).toEqual([{ type: "text", id: "text-1", text: "hello assistant" }])
|
||||
expect(state.messages[0].content).toEqual([{ type: "text", text: "hello assistant" }])
|
||||
})
|
||||
|
||||
test.skip("tool completion stores completed timestamp", () => {
|
||||
@@ -176,9 +176,9 @@ test.skip("tool completion stores completed timestamp", () => {
|
||||
sessionID,
|
||||
assistantMessageID,
|
||||
callID,
|
||||
tool: "bash",
|
||||
input: { command: "pwd" },
|
||||
provider: { executed: true, metadata: { fake: { source: "provider" } } },
|
||||
executed: true,
|
||||
state: { source: "provider" },
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
)
|
||||
@@ -195,7 +195,8 @@ test.skip("tool completion stores completed timestamp", () => {
|
||||
callID,
|
||||
structured: {},
|
||||
content: [{ type: "text", text: "/tmp" }],
|
||||
provider: { executed: true, metadata: { fake: { status: "done" } } },
|
||||
executed: true,
|
||||
resultState: { status: "done" },
|
||||
},
|
||||
} satisfies SessionEvent.Event),
|
||||
)
|
||||
@@ -205,7 +206,11 @@ test.skip("tool completion stores completed timestamp", () => {
|
||||
expect(state.messages[0].content[0]?.type).toBe("tool")
|
||||
if (state.messages[0].content[0]?.type !== "tool") return
|
||||
expect(state.messages[0].content[0].time.completed).toEqual(DateTime.makeUnsafe(4))
|
||||
expect(state.messages[0].content[0].provider).toEqual({ executed: true, metadata: { fake: { status: "done" } } })
|
||||
expect(state.messages[0].content[0]).toMatchObject({
|
||||
executed: true,
|
||||
providerState: { source: "provider" },
|
||||
providerResultState: { status: "done" },
|
||||
})
|
||||
})
|
||||
|
||||
test("compaction events reduce to compaction message only when completed", () => {
|
||||
|
||||
@@ -372,15 +372,16 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||
.add(
|
||||
HttpApiEndpoint.post("session.compact", "/api/session/:sessionID/compact", {
|
||||
params: { sessionID: Session.ID },
|
||||
success: HttpApiSchema.NoContent,
|
||||
error: [SessionNotFoundError, SessionBusyError, ServiceUnavailableError, UnknownError],
|
||||
payload: Schema.Struct({ id: SessionMessage.ID.pipe(Schema.optional) }),
|
||||
success: Schema.Struct({ data: SessionInput.Compaction }),
|
||||
error: [ConflictError, SessionNotFoundError],
|
||||
})
|
||||
.middleware(sessionLocationMiddleware)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.compact",
|
||||
summary: "Compact session",
|
||||
description: "Compact a session conversation.",
|
||||
description: "Queue a durable session compaction request.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 ContextUpdated = Event.durable({
|
||||
type: "session.context.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,41 +403,39 @@ 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 Admitted = Event.durable({
|
||||
type: "session.compaction.admitted",
|
||||
...options,
|
||||
schema: {
|
||||
...Base,
|
||||
inputID: SessionMessage.ID,
|
||||
},
|
||||
})
|
||||
export type Admitted = typeof Admitted.Type
|
||||
|
||||
export const Started = Event.durable({
|
||||
type: "session.compaction.started",
|
||||
...options,
|
||||
@@ -469,6 +466,13 @@ export namespace Compaction {
|
||||
},
|
||||
})
|
||||
export type Ended = typeof Ended.Type
|
||||
|
||||
export const Failed = Event.durable({
|
||||
type: "session.compaction.failed",
|
||||
...options,
|
||||
schema: Base,
|
||||
})
|
||||
export type Failed = typeof Failed.Type
|
||||
}
|
||||
|
||||
export namespace RevertEvent {
|
||||
@@ -481,7 +485,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 +497,10 @@ export const Definitions = Event.inventory(
|
||||
Forked,
|
||||
PromptPromoted,
|
||||
PromptAdmitted,
|
||||
ExecutionSettled,
|
||||
Execution.Started,
|
||||
Execution.Succeeded,
|
||||
Execution.Failed,
|
||||
Execution.Interrupted,
|
||||
ContextUpdated,
|
||||
Synthetic,
|
||||
Skill.Activated,
|
||||
@@ -515,10 +522,12 @@ export const Definitions = Event.inventory(
|
||||
Tool.Progress,
|
||||
Tool.Success,
|
||||
Tool.Failed,
|
||||
Retried,
|
||||
RetryScheduled,
|
||||
Compaction.Admitted,
|
||||
Compaction.Started,
|
||||
Compaction.Delta,
|
||||
Compaction.Ended,
|
||||
Compaction.Failed,
|
||||
RevertEvent.Staged,
|
||||
RevertEvent.Cleared,
|
||||
RevertEvent.Committed,
|
||||
|
||||
@@ -21,3 +21,22 @@ export const Admitted = Schema.Struct({
|
||||
timeCreated: DateTimeUtcFromMillis,
|
||||
promotedSeq: NonNegativeInt.pipe(optional),
|
||||
}).annotate({ identifier: "SessionInput.Admitted" })
|
||||
|
||||
export interface PromptEntry extends Schema.Schema.Type<typeof PromptEntry> {}
|
||||
export const PromptEntry = Schema.Struct({
|
||||
type: Schema.Literal("prompt"),
|
||||
...Admitted.fields,
|
||||
}).annotate({ identifier: "SessionInput.PromptEntry" })
|
||||
|
||||
export interface Compaction extends Schema.Schema.Type<typeof Compaction> {}
|
||||
export const Compaction = Schema.Struct({
|
||||
type: Schema.Literal("compaction"),
|
||||
admittedSeq: NonNegativeInt,
|
||||
id: SessionMessage.ID,
|
||||
sessionID: SessionID,
|
||||
timeCreated: DateTimeUtcFromMillis,
|
||||
handledSeq: NonNegativeInt.pipe(optional),
|
||||
}).annotate({ identifier: "SessionInput.Compaction" })
|
||||
|
||||
export const Entry = Schema.Union([PromptEntry, Compaction]).pipe(Schema.toTaggedUnion("type"))
|
||||
export type Entry = typeof Entry.Type
|
||||
|
||||
@@ -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,
|
||||
@@ -44,6 +45,7 @@ export const ModelSelected = Schema.Struct({
|
||||
...Base,
|
||||
type: Schema.Literal("model-switched"),
|
||||
model: Model.Ref,
|
||||
previous: Model.Ref.pipe(optional),
|
||||
}).annotate({ identifier: "Session.Message.ModelSelected" })
|
||||
|
||||
export interface User extends Schema.Schema.Type<typeof User> {}
|
||||
@@ -122,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" })
|
||||
|
||||
@@ -136,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,
|
||||
@@ -153,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),
|
||||
@@ -174,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,
|
||||
@@ -186,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,
|
||||
@@ -194,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),
|
||||
@@ -204,6 +210,7 @@ export const Assistant = Schema.Struct({
|
||||
export interface Compaction extends Schema.Schema.Type<typeof Compaction> {}
|
||||
export const Compaction = Schema.Struct({
|
||||
type: Schema.Literal("compaction"),
|
||||
status: Schema.Literals(["queued", "running", "completed", "failed"]),
|
||||
reason: Schema.Literals(["auto", "manual"]),
|
||||
summary: Schema.String,
|
||||
recent: Schema.String,
|
||||
|
||||
@@ -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"
|
||||
@@ -7,6 +7,7 @@ import { Project } from "../src/project.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"
|
||||
|
||||
@@ -67,4 +68,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")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -16,6 +16,8 @@ import { FileSystemV1 } from "../src/filesystem-v1.js"
|
||||
import { IdeEvent } from "../src/ide-event.js"
|
||||
import { McpEvent } from "../src/mcp-event.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"
|
||||
@@ -99,6 +101,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.context.updated.1",
|
||||
"session.synthetic.1",
|
||||
"session.skill.activated.1",
|
||||
@@ -117,9 +123,11 @@ 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.admitted.1",
|
||||
"session.compaction.started.1",
|
||||
"session.compaction.ended.1",
|
||||
"session.compaction.failed.1",
|
||||
"session.revert.staged.1",
|
||||
"session.revert.cleared.1",
|
||||
"session.revert.committed.1",
|
||||
@@ -130,4 +138,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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
@@ -30,6 +30,8 @@ type OpenApiDocument = {
|
||||
|
||||
const document = (await Bun.file("./openapi.json").json()) as OpenApiDocument
|
||||
const v2Document = (await Bun.file("./openapi-v2.json").json()) as OpenApiDocument
|
||||
normalizeComponentNames(v2Document)
|
||||
deduplicateEquivalentComponent(v2Document, "Shell", "Shell1")
|
||||
renameCollidingComponents(document, v2Document)
|
||||
document.paths = { ...document.paths, ...v2Document.paths }
|
||||
document.components = {
|
||||
@@ -60,7 +62,7 @@ if (schemas) {
|
||||
visit({ ...document, components: { ...document.components, schemas: undefined } })
|
||||
for (const name of Object.keys(schemas)) {
|
||||
if (
|
||||
/^(SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionSettled|SessionContextUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionReasoningStarted|SessionReasoningDelta|SessionReasoningEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetried|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)1$/.test(
|
||||
/^(SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionStarted|SessionExecutionSucceeded|SessionExecutionFailed|SessionExecutionInterrupted|SessionContextUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionReasoningStarted|SessionReasoningDelta|SessionReasoningEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetryScheduled|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)\d+$/.test(
|
||||
name,
|
||||
) &&
|
||||
!reachable.has(name)
|
||||
@@ -100,17 +102,28 @@ await createClient({
|
||||
const generatedTypesPath = "./src/v2/gen/types.gen.ts"
|
||||
const generatedTypes = await Bun.file(generatedTypesPath).text()
|
||||
if (
|
||||
/export type (SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionSettled|SessionContextUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionReasoningStarted|SessionReasoningDelta|SessionReasoningEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetried|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)1 =/.test(
|
||||
/export type (SessionAgentSelected|SessionModelSelected|SessionMoved|SessionRenamed|SessionForked|SessionPromptPromoted|SessionPromptAdmitted|SessionExecutionStarted|SessionExecutionSucceeded|SessionExecutionFailed|SessionExecutionInterrupted|SessionContextUpdated|SessionSynthetic|SessionSkillActivated|SessionShellStarted|SessionShellEnded|SessionStepStarted|SessionStepEnded|SessionStepFailed|SessionTextStarted|SessionTextDelta|SessionTextEnded|SessionReasoningStarted|SessionReasoningDelta|SessionReasoningEnded|SessionToolInputStarted|SessionToolInputDelta|SessionToolInputEnded|SessionToolCalled|SessionToolProgress|SessionToolSuccess|SessionToolFailed|SessionRetryScheduled|SessionCompactionStarted|SessionCompactionDelta|SessionCompactionEnded|SessionRevertStaged|SessionRevertCleared|SessionRevertCommitted)\d+ =/.test(
|
||||
generatedTypes,
|
||||
)
|
||||
) {
|
||||
throw new Error("Session history generated duplicate Session event variants")
|
||||
}
|
||||
const logTypesPatched = generatedTypes.replace(
|
||||
const sessionErrorTypesPatched = deduplicateEquivalentGeneratedTypes(
|
||||
generatedTypes,
|
||||
"SessionStructuredError",
|
||||
/^SessionStructuredError\d+$/,
|
||||
)
|
||||
const obsoleteSessionNext = [...sessionErrorTypesPatched.matchAll(/export type (SessionNext\w*) =/g)].map(
|
||||
(match) => match[1],
|
||||
)
|
||||
if (obsoleteSessionNext.length > 0) {
|
||||
throw new Error(`Obsolete SessionNext generated type noise reintroduced: ${obsoleteSessionNext.join(", ")}`)
|
||||
}
|
||||
const logTypesPatched = sessionErrorTypesPatched.replace(
|
||||
/(export type V2SessionLogData = \{[\s\S]*?query\?: \{\s*after\?: )string/,
|
||||
"$1number",
|
||||
)
|
||||
if (logTypesPatched === generatedTypes) {
|
||||
if (logTypesPatched === sessionErrorTypesPatched) {
|
||||
throw new Error("Session log numeric query patch did not apply")
|
||||
}
|
||||
const sessionListTypesPatched = logTypesPatched.replace(
|
||||
@@ -128,12 +141,21 @@ if (sessionMessagesTypesPatched === sessionListTypesPatched) {
|
||||
throw new Error("Session messages numeric query patch did not apply")
|
||||
}
|
||||
const eventSubscribeTypesPatched = sessionMessagesTypesPatched.replace(
|
||||
/(export type V2EventSubscribeResponses = \{\s*\/\*\*[\s\S]*?\*\/\s*200: )\{\s*id: string \| null;?\s*event: string;?\s*data: V2EventStreamV2;?\s*\};?/,
|
||||
/(export type V2EventSubscribeResponses = \{\s*\/\*\*[\s\S]*?\*\/\s*200: )\{\s*id: string \| null;?\s*event: string;?\s*data: V2EventStream(?:V2)?;?\s*\};?/,
|
||||
"$1V2Event",
|
||||
)
|
||||
if (eventSubscribeTypesPatched === sessionMessagesTypesPatched) {
|
||||
throw new Error("Event subscribe response patch did not apply")
|
||||
}
|
||||
if (/SessionStructuredError\d/.test(eventSubscribeTypesPatched)) {
|
||||
throw new Error("Session structured error generated a name-mangled duplicate")
|
||||
}
|
||||
if (/\bSessionNext\w*\b/.test(eventSubscribeTypesPatched)) {
|
||||
throw new Error("Obsolete SessionNext generated type noise reintroduced")
|
||||
}
|
||||
if (/export type Shell\d+V2 =/.test(eventSubscribeTypesPatched)) {
|
||||
throw new Error("Shell generated a name-mangled duplicate")
|
||||
}
|
||||
await Bun.write(generatedTypesPath, eventSubscribeTypesPatched)
|
||||
|
||||
const querySerializerPath = "./src/v2/gen/client/utils.gen.ts"
|
||||
@@ -206,6 +228,10 @@ function renameCollidingComponents(target: OpenApiDocument, source: OpenApiDocum
|
||||
const renames = new Map<string, string>()
|
||||
for (const name of Object.keys(sourceSchemas)) {
|
||||
if (!Object.hasOwn(targetSchemas, name)) continue
|
||||
if (JSON.stringify(normalizeSchema(sourceSchemas[name])) === JSON.stringify(normalizeSchema(targetSchemas[name]))) {
|
||||
delete sourceSchemas[name]
|
||||
continue
|
||||
}
|
||||
let renamed = `${name}V2`
|
||||
let index = 2
|
||||
while (Object.hasOwn(targetSchemas, renamed) || Object.hasOwn(sourceSchemas, renamed)) {
|
||||
@@ -225,6 +251,122 @@ function renameCollidingComponents(target: OpenApiDocument, source: OpenApiDocum
|
||||
source.paths = rewriteRefs(source.paths, renames) as Record<string, unknown> | undefined
|
||||
}
|
||||
|
||||
function normalizeComponentNames(document: OpenApiDocument) {
|
||||
let schemas = document.components?.schemas
|
||||
if (!schemas) return
|
||||
|
||||
for (const name of Object.keys(schemas)) {
|
||||
if (!Object.hasOwn(schemas, name)) continue
|
||||
const next = componentTypeName(name)
|
||||
if (next === name) continue
|
||||
if (schemas[next] !== undefined) {
|
||||
if (JSON.stringify(normalizeSchema(schemas[name])) !== JSON.stringify(normalizeSchema(schemas[next]))) continue
|
||||
const renames = new Map([[name, next]])
|
||||
schemas = rewriteRefs(schemas, renames) as Record<string, unknown>
|
||||
delete schemas[name]
|
||||
document.paths = rewriteRefs(document.paths, renames) as Record<string, unknown> | undefined
|
||||
continue
|
||||
}
|
||||
const renames = new Map([[name, next]])
|
||||
schemas = rewriteRefs(schemas, renames) as Record<string, unknown>
|
||||
schemas[next] = schemas[name]
|
||||
delete schemas[name]
|
||||
document.paths = rewriteRefs(document.paths, renames) as Record<string, unknown> | undefined
|
||||
}
|
||||
document.components = { ...document.components, schemas }
|
||||
}
|
||||
|
||||
function componentTypeName(name: string) {
|
||||
if (!name.includes(".")) return name
|
||||
return name
|
||||
.split(".")
|
||||
.filter((part) => !/^\d+$/.test(part))
|
||||
.map((part) => part.slice(0, 1).toUpperCase() + part.slice(1))
|
||||
.join("")
|
||||
}
|
||||
|
||||
function deduplicateEquivalentComponent(document: OpenApiDocument, canonical: string, duplicate: string) {
|
||||
const schemas = document.components?.schemas
|
||||
if (!schemas?.[canonical] || !schemas[duplicate]) return
|
||||
if (JSON.stringify(normalizeSchema(schemas[canonical])) !== JSON.stringify(normalizeSchema(schemas[duplicate]))) {
|
||||
throw new Error(`${duplicate} no longer has the same wire shape as ${canonical}`)
|
||||
}
|
||||
|
||||
const renames = new Map([[duplicate, canonical]])
|
||||
const rewritten = rewriteRefs(schemas, renames) as Record<string, unknown>
|
||||
delete rewritten[duplicate]
|
||||
document.components = { ...document.components, schemas: rewritten }
|
||||
document.paths = rewriteRefs(document.paths, renames) as Record<string, unknown> | undefined
|
||||
}
|
||||
|
||||
function deduplicateEquivalentGeneratedTypes(source: string, canonical: string, duplicates: RegExp) {
|
||||
const canonicalType = generatedType(source, canonical)
|
||||
if (!canonicalType) throw new Error(`Generated canonical type missing: ${canonical}`)
|
||||
const names = [...source.matchAll(/export type (\w+) =/g)]
|
||||
.map((match) => match[1])
|
||||
.filter((name): name is string => name !== undefined && duplicates.test(name))
|
||||
|
||||
return names.reduce((patched, name) => {
|
||||
const duplicate = generatedType(patched, name)
|
||||
const currentCanonical = generatedType(patched, canonical)
|
||||
if (!duplicate || !currentCanonical) throw new Error(`Generated type declaration missing while comparing ${name}`)
|
||||
if (normalizeGeneratedType(currentCanonical.shape) !== normalizeGeneratedType(duplicate.shape)) {
|
||||
throw new Error(`${name} no longer has the same generated type shape as ${canonical}`)
|
||||
}
|
||||
return (patched.slice(0, duplicate.start) + patched.slice(duplicate.end)).replaceAll(name, canonical)
|
||||
}, source)
|
||||
}
|
||||
|
||||
function generatedType(source: string, name: string) {
|
||||
const start = source.indexOf(`export type ${name} =`)
|
||||
if (start === -1) return undefined
|
||||
const next = source.indexOf("\n\nexport type ", start + 1)
|
||||
const shapeEnd = next === -1 ? source.length : next
|
||||
return {
|
||||
start,
|
||||
end: next === -1 ? source.length : next + 2,
|
||||
shape: source.slice(source.indexOf("=", start) + 1, shapeEnd),
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeGeneratedType(shape: string) {
|
||||
return shape.replaceAll(/\s/g, "")
|
||||
}
|
||||
|
||||
function normalizeSchema(value: unknown, key?: string): unknown {
|
||||
if (Array.isArray(value)) {
|
||||
const flattened =
|
||||
key === "anyOf"
|
||||
? value.flatMap((item) =>
|
||||
typeof item === "object" && item !== null && Object.keys(item).length === 1 && "anyOf" in item
|
||||
? Array.isArray(item.anyOf)
|
||||
? item.anyOf
|
||||
: [item]
|
||||
: [item],
|
||||
)
|
||||
: value
|
||||
const expanded =
|
||||
key === "anyOf"
|
||||
? flattened.flatMap((item) => {
|
||||
if (typeof item !== "object" || item === null || !("type" in item) || !("enum" in item)) return [item]
|
||||
if (Object.keys(item).some((property) => property !== "type" && property !== "enum")) return [item]
|
||||
if (!Array.isArray(item.enum)) return [item]
|
||||
return item.enum.map((member) => ({ type: item.type, enum: [member] }))
|
||||
})
|
||||
: flattened
|
||||
const normalized = expanded.map((item) => normalizeSchema(item))
|
||||
return [...new Map(normalized.map((item) => [JSON.stringify(item), item])).values()].sort((a, b) =>
|
||||
JSON.stringify(a).localeCompare(JSON.stringify(b)),
|
||||
)
|
||||
}
|
||||
if (typeof value !== "object" || value === null) return value
|
||||
return Object.fromEntries(
|
||||
Object.entries(value)
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.map(([property, child]) => [property, normalizeSchema(child, property)]),
|
||||
)
|
||||
}
|
||||
|
||||
function rewriteRefs(value: unknown, renames: Map<string, string>): unknown {
|
||||
if (Array.isArray(value)) return value.map((item) => rewriteRefs(item, renames))
|
||||
if (typeof value !== "object" || value === null) return value
|
||||
|
||||
@@ -76,8 +76,8 @@ import type {
|
||||
FindTextResponses,
|
||||
FormatterStatusErrors,
|
||||
FormatterStatusResponses,
|
||||
FormCreatePayload2,
|
||||
FormReply2,
|
||||
FormCreatePayloadV2,
|
||||
FormReply,
|
||||
GlobalConfigGetErrors,
|
||||
GlobalConfigGetResponses,
|
||||
GlobalConfigUpdateErrors,
|
||||
@@ -92,7 +92,7 @@ import type {
|
||||
GlobalUpgradeResponses,
|
||||
InstanceDisposeErrors,
|
||||
InstanceDisposeResponses,
|
||||
LocationRef2,
|
||||
LocationRefV2,
|
||||
LspStatusErrors,
|
||||
LspStatusResponses,
|
||||
McpAddErrors,
|
||||
@@ -113,7 +113,7 @@ import type {
|
||||
McpRemoteConfig,
|
||||
McpStatusErrors,
|
||||
McpStatusResponses,
|
||||
ModelRef2,
|
||||
ModelRef,
|
||||
MoveSessionDestination,
|
||||
OutputFormat,
|
||||
Part as Part2,
|
||||
@@ -130,8 +130,8 @@ import type {
|
||||
PermissionRespondErrors,
|
||||
PermissionRespondResponses,
|
||||
PermissionRuleset,
|
||||
PermissionV2Reply2,
|
||||
PermissionV2Source2,
|
||||
PermissionV2Reply,
|
||||
PermissionV2SourceV2,
|
||||
ProjectCommands,
|
||||
ProjectCurrentErrors,
|
||||
ProjectCurrentResponses,
|
||||
@@ -144,9 +144,9 @@ import type {
|
||||
ProjectListResponses,
|
||||
ProjectUpdateErrors,
|
||||
ProjectUpdateResponses,
|
||||
PromptAgentAttachment2,
|
||||
PromptInputFileAttachment2,
|
||||
PromptInputV2,
|
||||
PromptAgentAttachment,
|
||||
PromptInput,
|
||||
PromptInputFileAttachment,
|
||||
ProviderAuthErrors,
|
||||
ProviderAuthResponses,
|
||||
ProviderListErrors,
|
||||
@@ -178,14 +178,14 @@ import type {
|
||||
QuestionRejectResponses,
|
||||
QuestionReplyErrors,
|
||||
QuestionReplyResponses,
|
||||
QuestionV2Reply2,
|
||||
QuestionV2Reply,
|
||||
SessionAbortErrors,
|
||||
SessionAbortResponses,
|
||||
SessionChildrenErrors,
|
||||
SessionChildrenResponses,
|
||||
SessionCommandErrors,
|
||||
SessionCommandResponses,
|
||||
SessionContextEntryKey2,
|
||||
SessionContextEntryKeyV2,
|
||||
SessionCreateErrors,
|
||||
SessionCreateResponses,
|
||||
SessionDeleteErrors,
|
||||
@@ -458,7 +458,7 @@ import type {
|
||||
VcsDiffResponses,
|
||||
VcsGetErrors,
|
||||
VcsGetResponses,
|
||||
VcsMode2,
|
||||
VcsMode,
|
||||
VcsStatusErrors,
|
||||
VcsStatusResponses,
|
||||
WorktreeCreateErrors,
|
||||
@@ -5290,7 +5290,7 @@ export class Entry extends HeyApiClient {
|
||||
public remove<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
key: SessionContextEntryKey2
|
||||
key: SessionContextEntryKeyV2
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -5324,7 +5324,7 @@ export class Entry extends HeyApiClient {
|
||||
public put<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
key: SessionContextEntryKey2
|
||||
key: SessionContextEntryKeyV2
|
||||
value?: unknown
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
@@ -5393,7 +5393,7 @@ export class Form extends HeyApiClient {
|
||||
public create<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
formCreatePayload: FormCreatePayload2
|
||||
formCreatePayloadV2: FormCreatePayloadV2
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -5403,7 +5403,7 @@ export class Form extends HeyApiClient {
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "sessionID" },
|
||||
{ key: "formCreatePayload", map: "body" },
|
||||
{ key: "formCreatePayloadV2", map: "body" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -5491,7 +5491,7 @@ export class Form extends HeyApiClient {
|
||||
parameters: {
|
||||
sessionID: string
|
||||
formID: string
|
||||
formReply: FormReply2
|
||||
formReply: FormReply
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -5591,7 +5591,7 @@ export class Permission2 extends HeyApiClient {
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2Source2
|
||||
source?: PermissionV2SourceV2
|
||||
agent?: string | null
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
@@ -5672,7 +5672,7 @@ export class Permission2 extends HeyApiClient {
|
||||
parameters: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
reply?: PermissionV2Reply2
|
||||
reply?: PermissionV2Reply
|
||||
message?: string | null
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
@@ -5740,7 +5740,7 @@ export class Question2 extends HeyApiClient {
|
||||
parameters: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
questionV2Reply: QuestionV2Reply2
|
||||
questionV2Reply: QuestionV2Reply
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -5861,8 +5861,8 @@ export class Session3 extends HeyApiClient {
|
||||
parameters?: {
|
||||
id?: string | null
|
||||
agent?: string | null
|
||||
model?: ModelRef2 | null
|
||||
location?: LocationRef2 | null
|
||||
model?: ModelRef | null
|
||||
location?: LocationRefV2 | null
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -6004,7 +6004,7 @@ export class Session3 extends HeyApiClient {
|
||||
public switchModel<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
model?: ModelRef2
|
||||
model?: ModelRef
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -6079,7 +6079,7 @@ export class Session3 extends HeyApiClient {
|
||||
parameters: {
|
||||
sessionID: string
|
||||
id?: string | null
|
||||
prompt?: PromptInputV2
|
||||
prompt?: PromptInput
|
||||
delivery?: "steer" | "queue" | null
|
||||
resume?: boolean | null
|
||||
},
|
||||
@@ -6123,9 +6123,9 @@ export class Session3 extends HeyApiClient {
|
||||
command?: string
|
||||
arguments?: string | null
|
||||
agent?: string | null
|
||||
model?: ModelRef2 | null
|
||||
files?: Array<PromptInputFileAttachment2>
|
||||
agents?: Array<PromptAgentAttachment2>
|
||||
model?: ModelRef | null
|
||||
files?: Array<PromptInputFileAttachment>
|
||||
agents?: Array<PromptAgentAttachment>
|
||||
delivery?: "steer" | "queue" | null
|
||||
resume?: boolean | null
|
||||
},
|
||||
@@ -6282,19 +6282,35 @@ export class Session3 extends HeyApiClient {
|
||||
/**
|
||||
* Compact session
|
||||
*
|
||||
* Compact a session conversation.
|
||||
* Queue a durable session compaction request.
|
||||
*/
|
||||
public compact<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
id?: string | null
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams([parameters], [{ args: [{ in: "path", key: "sessionID" }] }])
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "sessionID" },
|
||||
{ in: "body", key: "id" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<V2SessionCompactResponses, V2SessionCompactErrors, ThrowOnError>({
|
||||
url: "/api/session/{sessionID}/compact",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6557,7 +6573,7 @@ export class Generate extends HeyApiClient {
|
||||
workspace?: string | null
|
||||
} | null
|
||||
prompt?: string
|
||||
model?: ModelRef2 | null
|
||||
model?: ModelRef | null
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
@@ -8011,7 +8027,7 @@ export class Vcs2 extends HeyApiClient {
|
||||
directory?: string | null
|
||||
workspace?: string | null
|
||||
} | null
|
||||
mode: VcsMode2
|
||||
mode: VcsMode
|
||||
context?: string | null
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
|
||||
+2677
-3278
@@ -24,7 +24,10 @@ export type Event =
|
||||
| EventSessionForked
|
||||
| EventSessionPromptPromoted
|
||||
| EventSessionPromptAdmitted
|
||||
| EventSessionExecutionSettled
|
||||
| EventSessionExecutionStarted
|
||||
| EventSessionExecutionSucceeded
|
||||
| EventSessionExecutionFailed
|
||||
| EventSessionExecutionInterrupted
|
||||
| EventSessionContextUpdated
|
||||
| EventSessionSynthetic
|
||||
| EventSessionSkillActivated
|
||||
@@ -46,10 +49,12 @@ export type Event =
|
||||
| EventSessionToolProgress
|
||||
| EventSessionToolSuccess
|
||||
| EventSessionToolFailed
|
||||
| EventSessionRetried
|
||||
| EventSessionRetryScheduled
|
||||
| EventSessionCompactionAdmitted
|
||||
| EventSessionCompactionStarted
|
||||
| EventSessionCompactionDelta
|
||||
| EventSessionCompactionEnded
|
||||
| EventSessionCompactionFailed
|
||||
| EventSessionRevertStaged
|
||||
| EventSessionRevertCleared
|
||||
| EventSessionRevertCommitted
|
||||
@@ -919,11 +924,32 @@ export type GlobalEvent = {
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.execution.settled"
|
||||
type: "session.execution.started"
|
||||
properties: {
|
||||
sessionID: string
|
||||
outcome: "success" | "failure" | "interrupted"
|
||||
error?: SessionErrorUnknown
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.execution.succeeded"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.execution.failed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.execution.interrupted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
reason: "user" | "shutdown" | "superseded"
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -994,7 +1020,7 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
finish: string
|
||||
finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
@@ -1015,7 +1041,7 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1024,7 +1050,7 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1033,7 +1059,7 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
@@ -1043,7 +1069,7 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
}
|
||||
}
|
||||
@@ -1053,8 +1079,8 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
ordinal: number
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1063,7 +1089,7 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
@@ -1073,9 +1099,9 @@ export type GlobalEvent = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1115,14 +1141,11 @@ export type GlobalEvent = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
tool: string
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1151,10 +1174,8 @@ export type GlobalEvent = {
|
||||
content: Array<LlmToolContent>
|
||||
outputPaths?: Array<string>
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1164,21 +1185,29 @@ export type GlobalEvent = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.retried"
|
||||
type: "session.retry.scheduled"
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
attempt: number
|
||||
error: SessionRetryError
|
||||
at: number
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.compaction.admitted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1207,6 +1236,13 @@ export type GlobalEvent = {
|
||||
recent: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.compaction.failed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "session.revert.staged"
|
||||
@@ -1227,7 +1263,7 @@ export type GlobalEvent = {
|
||||
type: "session.revert.committed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1723,6 +1759,10 @@ export type GlobalEvent = {
|
||||
| SyncEventSessionForked
|
||||
| SyncEventSessionPromptPromoted
|
||||
| SyncEventSessionPromptAdmitted
|
||||
| SyncEventSessionExecutionStarted
|
||||
| SyncEventSessionExecutionSucceeded
|
||||
| SyncEventSessionExecutionFailed
|
||||
| SyncEventSessionExecutionInterrupted
|
||||
| SyncEventSessionContextUpdated
|
||||
| SyncEventSessionSynthetic
|
||||
| SyncEventSessionSkillActivated
|
||||
@@ -1741,9 +1781,11 @@ export type GlobalEvent = {
|
||||
| SyncEventSessionToolProgress
|
||||
| SyncEventSessionToolSuccess
|
||||
| SyncEventSessionToolFailed
|
||||
| SyncEventSessionRetried
|
||||
| SyncEventSessionRetryScheduled
|
||||
| SyncEventSessionCompactionAdmitted
|
||||
| SyncEventSessionCompactionStarted
|
||||
| SyncEventSessionCompactionEnded
|
||||
| SyncEventSessionCompactionFailed
|
||||
| SyncEventSessionRevertStaged
|
||||
| SyncEventSessionRevertCleared
|
||||
| SyncEventSessionRevertCommitted
|
||||
@@ -2891,6 +2933,10 @@ export type SessionDurableEvent =
|
||||
| SessionForked
|
||||
| SessionPromptPromoted
|
||||
| SessionPromptAdmitted
|
||||
| SessionExecutionStarted
|
||||
| SessionExecutionSucceeded
|
||||
| SessionExecutionFailed
|
||||
| SessionExecutionInterrupted
|
||||
| SessionContextUpdated
|
||||
| SessionSynthetic
|
||||
| SessionSkillActivated
|
||||
@@ -2909,9 +2955,11 @@ export type SessionDurableEvent =
|
||||
| SessionToolProgress
|
||||
| SessionToolSuccess
|
||||
| SessionToolFailed
|
||||
| SessionRetried
|
||||
| SessionRetryScheduled
|
||||
| SessionCompactionAdmitted
|
||||
| SessionCompactionStarted
|
||||
| SessionCompactionEnded
|
||||
| SessionCompactionFailed
|
||||
| SessionRevertStaged
|
||||
| SessionRevertCleared
|
||||
| SessionRevertCommitted
|
||||
@@ -3032,7 +3080,10 @@ export type V2Event =
|
||||
| SessionForked
|
||||
| SessionPromptPromoted
|
||||
| SessionPromptAdmitted
|
||||
| SessionExecutionSettled
|
||||
| SessionExecutionStarted
|
||||
| SessionExecutionSucceeded
|
||||
| SessionExecutionFailed
|
||||
| SessionExecutionInterrupted
|
||||
| SessionContextUpdated
|
||||
| SessionSynthetic
|
||||
| SessionSkillActivated
|
||||
@@ -3054,10 +3105,12 @@ export type V2Event =
|
||||
| SessionToolProgress
|
||||
| SessionToolSuccess
|
||||
| SessionToolFailed
|
||||
| SessionRetried
|
||||
| SessionRetryScheduled
|
||||
| SessionCompactionAdmitted
|
||||
| SessionCompactionStarted
|
||||
| SessionCompactionDelta
|
||||
| SessionCompactionEnded
|
||||
| SessionCompactionFailed
|
||||
| SessionRevertStaged
|
||||
| SessionRevertCleared
|
||||
| SessionRevertCommitted
|
||||
@@ -3271,15 +3324,13 @@ export type PromptAgentAttachment = {
|
||||
source?: PromptSource
|
||||
}
|
||||
|
||||
export type SessionErrorUnknown = {
|
||||
type: "unknown"
|
||||
export type SessionStructuredError = {
|
||||
type: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type LlmProviderMetadata = {
|
||||
[key: string]: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
export type SessionMessageProviderState = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ToolTextContent = {
|
||||
@@ -3296,19 +3347,6 @@ export type ToolFileContent = {
|
||||
|
||||
export type LlmToolContent = ToolTextContent | ToolFileContent
|
||||
|
||||
export type SessionRetryError = {
|
||||
message: string
|
||||
statusCode?: number
|
||||
isRetryable: boolean
|
||||
responseHeaders?: {
|
||||
[key: string]: string
|
||||
}
|
||||
responseBody?: string
|
||||
metadata?: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
|
||||
export type FileDiff = {
|
||||
path: string
|
||||
status: "added" | "modified" | "deleted"
|
||||
@@ -3728,6 +3766,64 @@ export type SyncEventSessionPromptAdmitted = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionExecutionStarted = {
|
||||
type: "sync"
|
||||
id: string
|
||||
syncEvent: {
|
||||
type: "session.execution.started.1"
|
||||
id: string
|
||||
seq: number
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionExecutionSucceeded = {
|
||||
type: "sync"
|
||||
id: string
|
||||
syncEvent: {
|
||||
type: "session.execution.succeeded.1"
|
||||
id: string
|
||||
seq: number
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionExecutionFailed = {
|
||||
type: "sync"
|
||||
id: string
|
||||
syncEvent: {
|
||||
type: "session.execution.failed.1"
|
||||
id: string
|
||||
seq: number
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionExecutionInterrupted = {
|
||||
type: "sync"
|
||||
id: string
|
||||
syncEvent: {
|
||||
type: "session.execution.interrupted.1"
|
||||
id: string
|
||||
seq: number
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
reason: "user" | "shutdown" | "superseded"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionContextUpdated = {
|
||||
type: "sync"
|
||||
id: string
|
||||
@@ -3843,7 +3939,7 @@ export type SyncEventSessionStepEnded = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
finish: string
|
||||
finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
@@ -3871,7 +3967,7 @@ export type SyncEventSessionStepFailed = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3887,7 +3983,7 @@ export type SyncEventSessionTextStarted = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3903,7 +3999,7 @@ export type SyncEventSessionTextEnded = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
}
|
||||
}
|
||||
@@ -3920,8 +4016,8 @@ export type SyncEventSessionReasoningStarted = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
ordinal: number
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3937,9 +4033,9 @@ export type SyncEventSessionReasoningEnded = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3990,14 +4086,11 @@ export type SyncEventSessionToolCalled = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
tool: string
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4040,10 +4133,8 @@ export type SyncEventSessionToolSuccess = {
|
||||
content: Array<LlmToolContent>
|
||||
outputPaths?: Array<string>
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4060,28 +4151,43 @@ export type SyncEventSessionToolFailed = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionRetried = {
|
||||
export type SyncEventSessionRetryScheduled = {
|
||||
type: "sync"
|
||||
id: string
|
||||
syncEvent: {
|
||||
type: "session.retried.1"
|
||||
type: "session.retry.scheduled.1"
|
||||
id: string
|
||||
seq: number
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
attempt: number
|
||||
error: SessionRetryError
|
||||
at: number
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionCompactionAdmitted = {
|
||||
type: "sync"
|
||||
id: string
|
||||
syncEvent: {
|
||||
type: "session.compaction.admitted.1"
|
||||
id: string
|
||||
seq: number
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4118,6 +4224,20 @@ export type SyncEventSessionCompactionEnded = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionCompactionFailed = {
|
||||
type: "sync"
|
||||
id: string
|
||||
syncEvent: {
|
||||
type: "session.compaction.failed.1"
|
||||
id: string
|
||||
seq: number
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SyncEventSessionRevertStaged = {
|
||||
type: "sync"
|
||||
id: string
|
||||
@@ -4157,7 +4277,7 @@ export type SyncEventSessionRevertCommitted = {
|
||||
aggregateID: string
|
||||
data: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4288,6 +4408,15 @@ export type SessionInputAdmitted = {
|
||||
promotedSeq?: number
|
||||
}
|
||||
|
||||
export type SessionInputCompaction = {
|
||||
type: "compaction"
|
||||
admittedSeq: number
|
||||
id: string
|
||||
sessionID: string
|
||||
timeCreated: number
|
||||
handledSeq?: number
|
||||
}
|
||||
|
||||
export type SessionMessageAgentSelected = {
|
||||
id: string
|
||||
metadata?: {
|
||||
@@ -4310,6 +4439,7 @@ export type SessionMessageModelSelected = {
|
||||
}
|
||||
type: "model-switched"
|
||||
model: ModelRef
|
||||
previous?: ModelRef
|
||||
}
|
||||
|
||||
export type SessionMessageUser = {
|
||||
@@ -4386,15 +4516,13 @@ export type SessionMessageShell = {
|
||||
|
||||
export type SessionMessageAssistantText = {
|
||||
type: "text"
|
||||
id: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantReasoning = {
|
||||
type: "reasoning"
|
||||
id: string
|
||||
text: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
state?: SessionMessageProviderState
|
||||
time?: {
|
||||
created: number
|
||||
completed?: number
|
||||
@@ -4440,7 +4568,7 @@ export type SessionMessageToolStateError = {
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
result?: unknown
|
||||
}
|
||||
|
||||
@@ -4448,11 +4576,9 @@ export type SessionMessageAssistantTool = {
|
||||
type: "tool"
|
||||
id: string
|
||||
name: string
|
||||
provider?: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
resultMetadata?: LlmProviderMetadata
|
||||
}
|
||||
executed?: boolean
|
||||
providerState?: SessionMessageProviderState
|
||||
providerResultState?: SessionMessageProviderState
|
||||
state:
|
||||
| SessionMessageToolStatePending
|
||||
| SessionMessageToolStateRunning
|
||||
@@ -4466,6 +4592,12 @@ export type SessionMessageAssistantTool = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantRetry = {
|
||||
attempt: number
|
||||
at: number
|
||||
error: SessionStructuredError
|
||||
}
|
||||
|
||||
export type SessionMessageAssistant = {
|
||||
id: string
|
||||
metadata?: {
|
||||
@@ -4484,7 +4616,7 @@ export type SessionMessageAssistant = {
|
||||
end?: string
|
||||
files?: Array<string>
|
||||
}
|
||||
finish?: string
|
||||
finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
cost?: number
|
||||
tokens?: {
|
||||
input: number
|
||||
@@ -4495,11 +4627,13 @@ export type SessionMessageAssistant = {
|
||||
write: number
|
||||
}
|
||||
}
|
||||
error?: SessionErrorUnknown
|
||||
error?: SessionStructuredError
|
||||
retry?: SessionMessageAssistantRetry
|
||||
}
|
||||
|
||||
export type SessionMessageCompaction = {
|
||||
type: "compaction"
|
||||
status: "queued" | "running" | "completed" | "failed"
|
||||
reason: "auto" | "manual"
|
||||
summary: string
|
||||
recent: string
|
||||
@@ -4667,6 +4801,80 @@ export type SessionPromptAdmitted = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionStarted = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionSucceeded = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.succeeded"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionFailed = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionInterrupted = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.interrupted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
reason: "user" | "shutdown" | "superseded"
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionContextUpdated = {
|
||||
id: string
|
||||
created: number
|
||||
@@ -4811,7 +5019,7 @@ export type SessionStepEnded = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
finish: string
|
||||
finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
@@ -4843,7 +5051,7 @@ export type SessionStepFailed = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4863,7 +5071,7 @@ export type SessionTextStarted = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4883,7 +5091,7 @@ export type SessionTextEnded = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
}
|
||||
}
|
||||
@@ -4904,8 +5112,8 @@ export type SessionReasoningStarted = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
ordinal: number
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4925,9 +5133,9 @@ export type SessionReasoningEnded = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4990,14 +5198,11 @@ export type SessionToolCalled = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
tool: string
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5048,10 +5253,8 @@ export type SessionToolSuccess = {
|
||||
content: Array<LlmToolContent>
|
||||
outputPaths?: Array<string>
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5072,22 +5275,20 @@ export type SessionToolFailed = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRetried = {
|
||||
export type SessionRetryScheduled = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.retried"
|
||||
type: "session.retry.scheduled"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
@@ -5096,8 +5297,29 @@ export type SessionRetried = {
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
attempt: number
|
||||
error: SessionRetryError
|
||||
at: number
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionAdmitted = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.admitted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5141,6 +5363,24 @@ export type SessionCompactionEnded = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionFailed = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRevertStaged = {
|
||||
id: string
|
||||
created: number
|
||||
@@ -5193,7 +5433,7 @@ export type SessionRevertCommitted = {
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5724,21 +5964,6 @@ export type MessagePartRemoved = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionSettled = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.settled"
|
||||
location?: LocationRef
|
||||
data: {
|
||||
sessionID: string
|
||||
outcome: "success" | "failure" | "interrupted"
|
||||
error?: SessionErrorUnknown
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionTextDelta = {
|
||||
id: string
|
||||
created: number
|
||||
@@ -5750,7 +5975,7 @@ export type SessionTextDelta = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
@@ -5766,7 +5991,7 @@ export type SessionReasoningDelta = {
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
@@ -6826,13 +7051,37 @@ export type EventSessionPromptAdmitted = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionExecutionSettled = {
|
||||
export type EventSessionExecutionStarted = {
|
||||
id: string
|
||||
type: "session.execution.settled"
|
||||
type: "session.execution.started"
|
||||
properties: {
|
||||
sessionID: string
|
||||
outcome: "success" | "failure" | "interrupted"
|
||||
error?: SessionErrorUnknown
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionExecutionSucceeded = {
|
||||
id: string
|
||||
type: "session.execution.succeeded"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionExecutionFailed = {
|
||||
id: string
|
||||
type: "session.execution.failed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionExecutionInterrupted = {
|
||||
id: string
|
||||
type: "session.execution.interrupted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
reason: "user" | "shutdown" | "superseded"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6910,7 +7159,7 @@ export type EventSessionStepEnded = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
finish: string
|
||||
finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
@@ -6932,7 +7181,7 @@ export type EventSessionStepFailed = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6942,7 +7191,7 @@ export type EventSessionTextStarted = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6952,7 +7201,7 @@ export type EventSessionTextDelta = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
@@ -6963,7 +7212,7 @@ export type EventSessionTextEnded = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
}
|
||||
}
|
||||
@@ -6974,8 +7223,8 @@ export type EventSessionReasoningStarted = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
ordinal: number
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6985,7 +7234,7 @@ export type EventSessionReasoningDelta = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
@@ -6996,9 +7245,9 @@ export type EventSessionReasoningEnded = {
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
providerMetadata?: LlmProviderMetadata
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7042,14 +7291,11 @@ export type EventSessionToolCalled = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
tool: string
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
state?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7080,10 +7326,8 @@ export type EventSessionToolSuccess = {
|
||||
content: Array<LlmToolContent>
|
||||
outputPaths?: Array<string>
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7094,22 +7338,31 @@ export type EventSessionToolFailed = {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
error: SessionErrorUnknown
|
||||
error: SessionStructuredError
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata
|
||||
}
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionRetried = {
|
||||
export type EventSessionRetryScheduled = {
|
||||
id: string
|
||||
type: "session.retried"
|
||||
type: "session.retry.scheduled"
|
||||
properties: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
attempt: number
|
||||
error: SessionRetryError
|
||||
at: number
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionCompactionAdmitted = {
|
||||
id: string
|
||||
type: "session.compaction.admitted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7142,6 +7395,14 @@ export type EventSessionCompactionEnded = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionCompactionFailed = {
|
||||
id: string
|
||||
type: "session.compaction.failed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionRevertStaged = {
|
||||
id: string
|
||||
type: "session.revert.staged"
|
||||
@@ -7164,7 +7425,7 @@ export type EventSessionRevertCommitted = {
|
||||
type: "session.revert.committed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7710,11 +7971,6 @@ export type BadRequestError = {
|
||||
}
|
||||
}
|
||||
|
||||
export type UnauthorizedErrorV2 = {
|
||||
_tag: "UnauthorizedError"
|
||||
message: string
|
||||
}
|
||||
|
||||
export type InvalidRequestErrorV2 = {
|
||||
_tag: "InvalidRequestError"
|
||||
message: string
|
||||
@@ -7722,112 +7978,6 @@ export type InvalidRequestErrorV2 = {
|
||||
field?: string | null
|
||||
}
|
||||
|
||||
export type LocationInfo2 = {
|
||||
directory: string
|
||||
workspaceID?: string
|
||||
project: {
|
||||
id: string
|
||||
directory: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ModelRef2 = {
|
||||
id: string
|
||||
providerID: string
|
||||
variant?: string
|
||||
}
|
||||
|
||||
export type ProviderSettings2 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type ProviderRequest2 = {
|
||||
settings: ProviderSettings2
|
||||
headers: {
|
||||
[key: string]: string
|
||||
}
|
||||
body: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type AgentColor2 = string | "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info"
|
||||
|
||||
export type PermissionV2Effect2 = "allow" | "deny" | "ask"
|
||||
|
||||
export type PermissionV2Rule2 = {
|
||||
action: string
|
||||
resource: string
|
||||
effect: PermissionV2Effect2
|
||||
}
|
||||
|
||||
export type PermissionV2Ruleset2 = Array<PermissionV2Rule2>
|
||||
|
||||
export type AgentV2Info2 = {
|
||||
id: string
|
||||
model?: ModelRef2
|
||||
request: ProviderRequest2
|
||||
system?: string
|
||||
description?: string
|
||||
mode: "subagent" | "primary" | "all"
|
||||
hidden: boolean
|
||||
color?: AgentColor2
|
||||
steps?: number
|
||||
permissions: PermissionV2Ruleset2
|
||||
}
|
||||
|
||||
export type PluginInfo2 = {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type LocationRef2 = {
|
||||
directory: string
|
||||
workspaceID?: string
|
||||
}
|
||||
|
||||
export type FileDiff2 = {
|
||||
path: string
|
||||
status: "added" | "modified" | "deleted"
|
||||
additions: number
|
||||
deletions: number
|
||||
patch: string
|
||||
}
|
||||
|
||||
export type RevertState2 = {
|
||||
messageID: string
|
||||
partID?: string
|
||||
snapshot?: string
|
||||
diff?: string
|
||||
files?: Array<FileDiff2>
|
||||
}
|
||||
|
||||
export type SessionV2Info2 = {
|
||||
id: string
|
||||
parentID?: string
|
||||
projectID: string
|
||||
agent?: string
|
||||
model?: ModelRef2
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
updated: number
|
||||
archived?: number
|
||||
}
|
||||
title: string
|
||||
location: LocationRef2
|
||||
subpath?: string
|
||||
revert?: RevertState2
|
||||
}
|
||||
|
||||
/**
|
||||
* Durable log seq each session's snapshot was computed at. Attach a live log read after the watermark to compose fetch and stream gap-free; apply a snapshot only where its watermark is at or beyond already-applied events. Sessions without durable events are absent.
|
||||
*/
|
||||
@@ -7836,7 +7986,7 @@ export type SessionWatermarksV2 = {
|
||||
}
|
||||
|
||||
export type SessionsResponseV2 = {
|
||||
data: Array<SessionV2Info2>
|
||||
data: Array<SessionV2InfoV2>
|
||||
watermarks: SessionWatermarksV2
|
||||
cursor: {
|
||||
previous?: string | null
|
||||
@@ -7844,11 +7994,6 @@ export type SessionsResponseV2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type InvalidCursorErrorV2 = {
|
||||
_tag: "InvalidCursorError"
|
||||
message: string
|
||||
}
|
||||
|
||||
export type InvalidRequestError1 = {
|
||||
_tag: "InvalidRequestError"
|
||||
message: string
|
||||
@@ -7856,101 +8001,12 @@ export type InvalidRequestError1 = {
|
||||
field?: string | null
|
||||
}
|
||||
|
||||
export type SessionActiveV2 = {
|
||||
type: "running"
|
||||
}
|
||||
|
||||
export type SessionNotFoundErrorV2 = {
|
||||
_tag: "SessionNotFoundError"
|
||||
sessionID: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type MessageNotFoundErrorV2 = {
|
||||
_tag: "MessageNotFoundError"
|
||||
sessionID: string
|
||||
messageID: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type PromptSource2 = {
|
||||
start: number
|
||||
end: number
|
||||
text: string
|
||||
}
|
||||
|
||||
export type PromptInputFileAttachment2 = {
|
||||
uri: string
|
||||
name?: string
|
||||
description?: string
|
||||
source?: PromptSource2
|
||||
}
|
||||
|
||||
export type PromptAgentAttachment2 = {
|
||||
name: string
|
||||
source?: PromptSource2
|
||||
}
|
||||
|
||||
export type PromptInputV2 = {
|
||||
text: string
|
||||
files?: Array<PromptInputFileAttachment2>
|
||||
agents?: Array<PromptAgentAttachment2>
|
||||
}
|
||||
|
||||
export type PromptFileAttachment2 = {
|
||||
uri: string
|
||||
mime: string
|
||||
name?: string
|
||||
description?: string
|
||||
source?: PromptSource2
|
||||
}
|
||||
|
||||
export type PromptV2 = {
|
||||
text: string
|
||||
files?: Array<PromptFileAttachment2>
|
||||
agents?: Array<PromptAgentAttachment2>
|
||||
}
|
||||
|
||||
export type SessionInputAdmitted2 = {
|
||||
admittedSeq: number
|
||||
id: string
|
||||
sessionID: string
|
||||
prompt: PromptV2
|
||||
delivery: "steer" | "queue"
|
||||
timeCreated: number
|
||||
promotedSeq?: number
|
||||
}
|
||||
|
||||
export type ConflictErrorV2 = {
|
||||
_tag: "ConflictError"
|
||||
message: string
|
||||
resource?: string | null
|
||||
}
|
||||
|
||||
export type CommandNotFoundErrorV2 = {
|
||||
_tag: "CommandNotFoundError"
|
||||
command: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type CommandEvaluationErrorV2 = {
|
||||
_tag: "CommandEvaluationError"
|
||||
command: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type SkillNotFoundErrorV2 = {
|
||||
_tag: "SkillNotFoundError"
|
||||
skill: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type SessionBusyErrorV2 = {
|
||||
_tag: "SessionBusyError"
|
||||
sessionID: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type ServiceUnavailableErrorV2 = {
|
||||
_tag: "ServiceUnavailableError"
|
||||
message: string
|
||||
@@ -7963,83 +8019,6 @@ export type UnknownErrorV2 = {
|
||||
ref?: string | null
|
||||
}
|
||||
|
||||
export type SessionMessageAgentSelected2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "agent-switched"
|
||||
agent: string
|
||||
}
|
||||
|
||||
export type SessionMessageModelSelected2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "model-switched"
|
||||
model: ModelRef2
|
||||
}
|
||||
|
||||
export type SessionMessageUser2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
text: string
|
||||
files?: Array<PromptFileAttachment2>
|
||||
agents?: Array<PromptAgentAttachment2>
|
||||
type: "user"
|
||||
}
|
||||
|
||||
export type SessionMessageSynthetic2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
sessionID: string
|
||||
text: string
|
||||
description?: string
|
||||
type: "synthetic"
|
||||
}
|
||||
|
||||
export type SessionMessageSystem2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "system"
|
||||
text: string
|
||||
}
|
||||
|
||||
export type SessionMessageSkill2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "skill"
|
||||
name: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export type ShellV2 = {
|
||||
id: string
|
||||
status: "running" | "exited" | "timeout" | "killed"
|
||||
@@ -8058,975 +8037,8 @@ export type ShellV2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageShell2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
type: "shell"
|
||||
shell: ShellV2
|
||||
output?: {
|
||||
output: string
|
||||
cursor: number
|
||||
size: number
|
||||
truncated: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantText2 = {
|
||||
type: "text"
|
||||
id: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export type LlmProviderMetadata2 = {
|
||||
[key: string]: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantReasoning2 = {
|
||||
type: "reasoning"
|
||||
id: string
|
||||
text: string
|
||||
providerMetadata?: LlmProviderMetadata2
|
||||
time?: {
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageToolStatePending2 = {
|
||||
status: "pending"
|
||||
input: string
|
||||
}
|
||||
|
||||
export type ToolTextContent2 = {
|
||||
type: "text"
|
||||
text: string
|
||||
}
|
||||
|
||||
export type ToolFileContent2 = {
|
||||
type: "file"
|
||||
uri: string
|
||||
mime: string
|
||||
name?: string
|
||||
}
|
||||
|
||||
export type LlmToolContent2 = ToolTextContent2 | ToolFileContent2
|
||||
|
||||
export type SessionMessageToolStateRunning2 = {
|
||||
status: "running"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
content: Array<LlmToolContent2>
|
||||
}
|
||||
|
||||
export type SessionMessageToolStateCompleted2 = {
|
||||
status: "completed"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
attachments?: Array<PromptFileAttachment2>
|
||||
content: Array<LlmToolContent2>
|
||||
outputPaths?: Array<string>
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
result?: unknown
|
||||
}
|
||||
|
||||
export type SessionErrorUnknown2 = {
|
||||
type: "unknown"
|
||||
message: string
|
||||
}
|
||||
|
||||
export type SessionMessageToolStateError2 = {
|
||||
status: "error"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
content: Array<LlmToolContent2>
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
error: SessionErrorUnknown2
|
||||
result?: unknown
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantTool2 = {
|
||||
type: "tool"
|
||||
id: string
|
||||
name: string
|
||||
provider?: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata2
|
||||
resultMetadata?: LlmProviderMetadata2
|
||||
}
|
||||
state:
|
||||
| SessionMessageToolStatePending2
|
||||
| SessionMessageToolStateRunning2
|
||||
| SessionMessageToolStateCompleted2
|
||||
| SessionMessageToolStateError2
|
||||
time: {
|
||||
created: number
|
||||
ran?: number
|
||||
completed?: number
|
||||
pruned?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageAssistant2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
type: "assistant"
|
||||
agent: string
|
||||
model: ModelRef2
|
||||
content: Array<SessionMessageAssistantText2 | SessionMessageAssistantReasoning2 | SessionMessageAssistantTool2>
|
||||
snapshot?: {
|
||||
start?: string
|
||||
end?: string
|
||||
files?: Array<string>
|
||||
}
|
||||
finish?: string
|
||||
cost?: number
|
||||
tokens?: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
error?: SessionErrorUnknown2
|
||||
}
|
||||
|
||||
export type SessionMessageCompaction2 = {
|
||||
type: "compaction"
|
||||
reason: "auto" | "manual"
|
||||
summary: string
|
||||
recent: string
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessage2 =
|
||||
| SessionMessageAgentSelected2
|
||||
| SessionMessageModelSelected2
|
||||
| SessionMessageUser2
|
||||
| SessionMessageSynthetic2
|
||||
| SessionMessageSystem2
|
||||
| SessionMessageSkill2
|
||||
| SessionMessageShell2
|
||||
| SessionMessageAssistant2
|
||||
| SessionMessageCompaction2
|
||||
|
||||
/**
|
||||
* Context entry key (lowercase alphanumerics plus . _ -)
|
||||
*/
|
||||
export type SessionContextEntryKey2 = string
|
||||
|
||||
export type SessionContextEntryInfo2 = {
|
||||
key: SessionContextEntryKey2
|
||||
value: unknown
|
||||
}
|
||||
|
||||
export type SessionAgentSelected2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.agent.selected"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
agent: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionModelSelected2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.model.selected"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
model: ModelRef2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMoved2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.moved"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
location: LocationRef2
|
||||
subpath?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRenamed2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.renamed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
title: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionForked2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.forked"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
parentID: string
|
||||
from?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionPromptPromoted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.prompt.promoted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionPromptAdmitted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.prompt.admitted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
prompt: PromptV2
|
||||
delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionContextUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.context.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionSynthetic2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.synthetic"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
text: string
|
||||
description?: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionSkillActivated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.skill.activated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
name: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Shell1V2 = {
|
||||
id: string
|
||||
status: "running" | "exited" | "timeout" | "killed"
|
||||
command: string
|
||||
cwd: string
|
||||
shell: string
|
||||
file: string
|
||||
pid?: number
|
||||
exit?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
metadata: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
started: number | "NaN" | "Infinity" | "-Infinity"
|
||||
completed?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionShellStarted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.shell.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
shell: Shell1V2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionShellEnded2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.shell.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
shell: Shell1V2
|
||||
output: {
|
||||
output: string
|
||||
cursor: number
|
||||
size: number
|
||||
truncated: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionStepStarted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.step.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
agent: string
|
||||
model: ModelRef2
|
||||
snapshot?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionStepEnded2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.step.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
finish: string
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
snapshot?: string
|
||||
files?: Array<string>
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionStepFailed2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.step.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
error: SessionErrorUnknown2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionTextStarted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.text.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionTextEnded2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.text.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type LlmProviderMetadata3 = {
|
||||
[key: string]: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionReasoningStarted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.reasoning.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
providerMetadata?: LlmProviderMetadata3
|
||||
}
|
||||
}
|
||||
|
||||
export type LlmProviderMetadata4 = {
|
||||
[key: string]: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionReasoningEnded2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.reasoning.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
text: string
|
||||
providerMetadata?: LlmProviderMetadata4
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolInputStarted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.input.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolInputEnded2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.input.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type LlmProviderMetadata5 = {
|
||||
[key: string]: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolCalled2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.called"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
tool: string
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolProgress2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.progress"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
content: Array<LlmToolContent2>
|
||||
}
|
||||
}
|
||||
|
||||
export type LlmProviderMetadata6 = {
|
||||
[key: string]: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolSuccess2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.success"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
content: Array<LlmToolContent2>
|
||||
outputPaths?: Array<string>
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata6
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type LlmProviderMetadata7 = {
|
||||
[key: string]: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolFailed2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
error: SessionErrorUnknown2
|
||||
result?: unknown
|
||||
provider: {
|
||||
executed: boolean
|
||||
metadata?: LlmProviderMetadata7
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRetryError2 = {
|
||||
message: string
|
||||
statusCode?: number
|
||||
isRetryable: boolean
|
||||
responseHeaders?: {
|
||||
[key: string]: string
|
||||
}
|
||||
responseBody?: string
|
||||
metadata?: {
|
||||
[key: string]: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRetried2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.retried"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
attempt: number
|
||||
error: SessionRetryError2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionStarted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
reason: "auto" | "manual"
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionEnded2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
reason: "auto" | "manual"
|
||||
text: string
|
||||
recent: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRevertStaged2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.revert.staged"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
revert: RevertState2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRevertCleared2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.revert.cleared"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRevertCommitted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.revert.committed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionDurableEventV2 =
|
||||
| SessionAgentSelected2
|
||||
| SessionModelSelected2
|
||||
| SessionMoved2
|
||||
| SessionRenamed2
|
||||
| SessionForked2
|
||||
| SessionPromptPromoted2
|
||||
| SessionPromptAdmitted2
|
||||
| SessionContextUpdated2
|
||||
| SessionSynthetic2
|
||||
| SessionSkillActivated2
|
||||
| SessionShellStarted2
|
||||
| SessionShellEnded2
|
||||
| SessionStepStarted2
|
||||
| SessionStepEnded2
|
||||
| SessionStepFailed2
|
||||
| SessionTextStarted2
|
||||
| SessionTextEnded2
|
||||
| SessionReasoningStarted2
|
||||
| SessionReasoningEnded2
|
||||
| SessionToolInputStarted2
|
||||
| SessionToolInputEnded2
|
||||
| SessionToolCalled2
|
||||
| SessionToolProgress2
|
||||
| SessionToolSuccess2
|
||||
| SessionToolFailed2
|
||||
| SessionRetried2
|
||||
| SessionCompactionStarted2
|
||||
| SessionCompactionEnded2
|
||||
| SessionRevertStaged2
|
||||
| SessionRevertCleared2
|
||||
| SessionRevertCommitted2
|
||||
|
||||
/**
|
||||
* Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq.
|
||||
*/
|
||||
export type EventLogSynced2 = {
|
||||
type: "log.synced"
|
||||
aggregateID: string
|
||||
seq?: number
|
||||
}
|
||||
|
||||
export type SessionLogItemV2 = SessionDurableEventV2 | EventLogSynced2
|
||||
|
||||
export type SessionLogItemStreamV2 = string
|
||||
|
||||
export type SessionMessagesResponseV2 = {
|
||||
data: Array<SessionMessage2>
|
||||
data: Array<SessionMessage>
|
||||
watermark?: number
|
||||
cursor: {
|
||||
previous?: string | null
|
||||
@@ -9034,588 +8046,6 @@ export type SessionMessagesResponseV2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type ModelApi2 =
|
||||
| {
|
||||
id: string
|
||||
type: "aisdk"
|
||||
package: string
|
||||
url?: string
|
||||
settings?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
type: "native"
|
||||
url?: string
|
||||
settings: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type ModelCapabilities2 = {
|
||||
tools: boolean
|
||||
input: Array<string>
|
||||
output: Array<string>
|
||||
}
|
||||
|
||||
export type ModelCost2 = {
|
||||
tier?: {
|
||||
type: "context"
|
||||
size: number
|
||||
}
|
||||
input: number
|
||||
output: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ModelV2Info2 = {
|
||||
id: string
|
||||
providerID: string
|
||||
family?: string
|
||||
name: string
|
||||
api: ModelApi2
|
||||
capabilities: ModelCapabilities2
|
||||
request: {
|
||||
settings: ProviderSettings2
|
||||
headers: {
|
||||
[key: string]: string
|
||||
}
|
||||
body: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
variant?: string
|
||||
}
|
||||
variants: Array<{
|
||||
id: string
|
||||
settings: ProviderSettings2
|
||||
headers: {
|
||||
[key: string]: string
|
||||
}
|
||||
body: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}>
|
||||
time: {
|
||||
released: number
|
||||
}
|
||||
cost: Array<ModelCost2>
|
||||
status: "alpha" | "beta" | "deprecated" | "active"
|
||||
enabled: boolean
|
||||
limit: {
|
||||
context: number
|
||||
input?: number
|
||||
output: number
|
||||
}
|
||||
}
|
||||
|
||||
export type GenerateTextResponseV2 = {
|
||||
data: {
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ProviderAisdk2 = {
|
||||
type: "aisdk"
|
||||
package: string
|
||||
url?: string
|
||||
settings?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type ProviderNative2 = {
|
||||
type: "native"
|
||||
url?: string
|
||||
settings: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type ProviderApi2 = ProviderAisdk2 | ProviderNative2
|
||||
|
||||
export type ProviderV2Info2 = {
|
||||
id: string
|
||||
integrationID?: string
|
||||
name: string
|
||||
disabled?: boolean
|
||||
api: ProviderApi2
|
||||
request: ProviderRequest2
|
||||
}
|
||||
|
||||
export type ProviderNotFoundErrorV2 = {
|
||||
_tag: "ProviderNotFoundError"
|
||||
providerID: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type IntegrationWhen2 = {
|
||||
key: string
|
||||
op: "eq" | "neq"
|
||||
value: string
|
||||
}
|
||||
|
||||
export type IntegrationTextPrompt2 = {
|
||||
type: "text"
|
||||
key: string
|
||||
message: string
|
||||
placeholder?: string
|
||||
when?: IntegrationWhen2
|
||||
}
|
||||
|
||||
export type IntegrationSelectPrompt2 = {
|
||||
type: "select"
|
||||
key: string
|
||||
message: string
|
||||
options: Array<{
|
||||
label: string
|
||||
value: string
|
||||
hint?: string
|
||||
}>
|
||||
when?: IntegrationWhen2
|
||||
}
|
||||
|
||||
export type IntegrationOAuthMethod2 = {
|
||||
id: string
|
||||
type: "oauth"
|
||||
label: string
|
||||
prompts?: Array<IntegrationTextPrompt2 | IntegrationSelectPrompt2>
|
||||
}
|
||||
|
||||
export type IntegrationKeyMethod2 = {
|
||||
type: "key"
|
||||
label?: string
|
||||
}
|
||||
|
||||
export type IntegrationEnvMethod2 = {
|
||||
type: "env"
|
||||
names: Array<string>
|
||||
}
|
||||
|
||||
export type IntegrationMethod2 = IntegrationOAuthMethod2 | IntegrationKeyMethod2 | IntegrationEnvMethod2
|
||||
|
||||
export type ConnectionCredentialInfo2 = {
|
||||
type: "credential"
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export type ConnectionEnvInfo2 = {
|
||||
type: "env"
|
||||
name: string
|
||||
}
|
||||
|
||||
export type ConnectionInfo2 = ConnectionCredentialInfo2 | ConnectionEnvInfo2
|
||||
|
||||
export type IntegrationInfo2 = {
|
||||
id: string
|
||||
name: string
|
||||
methods: Array<IntegrationMethod2>
|
||||
connections: Array<ConnectionInfo2>
|
||||
}
|
||||
|
||||
export type IntegrationAttempt2 = {
|
||||
attemptID: string
|
||||
url: string
|
||||
instructions: string
|
||||
mode: "auto" | "code"
|
||||
time: {
|
||||
created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
|
||||
export type IntegrationAttemptStatus2 =
|
||||
| {
|
||||
status: "pending"
|
||||
time: {
|
||||
created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
status: "complete"
|
||||
time: {
|
||||
created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
status: "failed"
|
||||
message: string
|
||||
time: {
|
||||
created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
| {
|
||||
status: "expired"
|
||||
time: {
|
||||
created: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
expires: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
}
|
||||
|
||||
export type McpStatusConnected3 = {
|
||||
status: "connected"
|
||||
}
|
||||
|
||||
export type McpStatusPending2 = {
|
||||
status: "pending"
|
||||
}
|
||||
|
||||
export type McpStatusDisabled3 = {
|
||||
status: "disabled"
|
||||
}
|
||||
|
||||
export type McpStatusFailed3 = {
|
||||
status: "failed"
|
||||
error: string
|
||||
}
|
||||
|
||||
export type McpStatusNeedsAuth3 = {
|
||||
status: "needs_auth"
|
||||
}
|
||||
|
||||
export type McpStatusNeedsClientRegistration3 = {
|
||||
status: "needs_client_registration"
|
||||
error: string
|
||||
}
|
||||
|
||||
export type McpServer2 = {
|
||||
name: string
|
||||
status:
|
||||
| McpStatusConnected3
|
||||
| McpStatusPending2
|
||||
| McpStatusDisabled3
|
||||
| McpStatusFailed3
|
||||
| McpStatusNeedsAuth3
|
||||
| McpStatusNeedsClientRegistration3
|
||||
integrationID?: string
|
||||
}
|
||||
|
||||
export type ProjectCurrent2 = {
|
||||
id: string
|
||||
directory: string
|
||||
}
|
||||
|
||||
export type ProjectDirectory2 = {
|
||||
directory: string
|
||||
strategy?: string
|
||||
}
|
||||
|
||||
export type ProjectDirectories2 = Array<ProjectDirectory2>
|
||||
|
||||
export type FormMetadata2 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type FormWhen2 = {
|
||||
key: string
|
||||
op: "eq" | "neq"
|
||||
value: string | number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" | boolean
|
||||
}
|
||||
|
||||
export type FormOption2 = {
|
||||
value: string
|
||||
label: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type FormStringField2 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen2>
|
||||
type: "string"
|
||||
format?: "email" | "uri" | "date" | "date-time"
|
||||
minLength?: number
|
||||
maxLength?: number
|
||||
pattern?: string
|
||||
placeholder?: string
|
||||
default?: string
|
||||
options?: Array<FormOption2>
|
||||
custom?: boolean
|
||||
}
|
||||
|
||||
export type FormNumberField3 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen2>
|
||||
type: "number"
|
||||
minimum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
maximum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
default?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
|
||||
export type FormIntegerField3 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen2>
|
||||
type: "integer"
|
||||
minimum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
maximum?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
default?: number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN"
|
||||
}
|
||||
|
||||
export type FormBooleanField2 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen2>
|
||||
type: "boolean"
|
||||
default?: boolean
|
||||
}
|
||||
|
||||
export type FormMultiselectField2 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen2>
|
||||
type: "multiselect"
|
||||
options: Array<FormOption2>
|
||||
minItems?: number
|
||||
maxItems?: number
|
||||
custom?: boolean
|
||||
default?: Array<string>
|
||||
}
|
||||
|
||||
export type FormFormInfo2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata2
|
||||
mode: "form"
|
||||
fields: Array<FormStringField2 | FormNumberField3 | FormIntegerField3 | FormBooleanField2 | FormMultiselectField2>
|
||||
}
|
||||
|
||||
export type FormUrlInfo2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata2
|
||||
mode: "url"
|
||||
url: string
|
||||
}
|
||||
|
||||
export type FormCreatePayload2 = {
|
||||
id?: string | null
|
||||
title?: string
|
||||
metadata?: FormMetadata2
|
||||
mode: "form" | "url"
|
||||
fields?: Array<
|
||||
FormStringField2 | FormNumberField3 | FormIntegerField3 | FormBooleanField2 | FormMultiselectField2
|
||||
> | null
|
||||
url?: string | null
|
||||
}
|
||||
|
||||
export type FormNotFoundErrorV2 = {
|
||||
_tag: "FormNotFoundError"
|
||||
id: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type FormValue2 =
|
||||
| string
|
||||
| number
|
||||
| "NaN"
|
||||
| "Infinity"
|
||||
| "-Infinity"
|
||||
| "Infinity"
|
||||
| "-Infinity"
|
||||
| "NaN"
|
||||
| boolean
|
||||
| Array<string>
|
||||
|
||||
export type FormAnswer2 = {
|
||||
[key: string]: FormValue2
|
||||
}
|
||||
|
||||
export type FormState2 =
|
||||
| {
|
||||
status: "pending"
|
||||
}
|
||||
| {
|
||||
status: "answered"
|
||||
answer: FormAnswer2
|
||||
}
|
||||
| {
|
||||
status: "cancelled"
|
||||
}
|
||||
|
||||
export type FormReply2 = {
|
||||
answer: FormAnswer2
|
||||
}
|
||||
|
||||
export type FormAlreadySettledErrorV2 = {
|
||||
_tag: "FormAlreadySettledError"
|
||||
id: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type FormInvalidAnswerErrorV2 = {
|
||||
_tag: "FormInvalidAnswerError"
|
||||
id: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type PermissionV2Source2 = {
|
||||
type: "tool"
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
|
||||
export type PermissionV2Request2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
action: string
|
||||
resources: Array<string>
|
||||
save?: Array<string>
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2Source2
|
||||
}
|
||||
|
||||
export type PermissionSavedInfo2 = {
|
||||
id: string
|
||||
projectID: string
|
||||
action: string
|
||||
resource: string
|
||||
}
|
||||
|
||||
export type PermissionNotFoundErrorV2 = {
|
||||
_tag: "PermissionNotFoundError"
|
||||
requestID: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type PermissionV2Reply2 = "once" | "always" | "reject"
|
||||
|
||||
export type FileSystemEntry2 = {
|
||||
path: string
|
||||
type: "file" | "directory"
|
||||
}
|
||||
|
||||
export type CommandV2Info2 = {
|
||||
name: string
|
||||
template: string
|
||||
description?: string
|
||||
agent?: string
|
||||
model?: ModelRef2
|
||||
subtask?: boolean
|
||||
}
|
||||
|
||||
export type SkillV2Info2 = {
|
||||
name: string
|
||||
description?: string
|
||||
slash?: boolean
|
||||
autoinvoke?: boolean
|
||||
location: string
|
||||
content: string
|
||||
}
|
||||
|
||||
export type ModelsDevRefreshed2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "models-dev.refreshed"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type IntegrationUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "integration.updated"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type IntegrationConnectionUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "integration.connection.updated"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
integrationID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type CatalogUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "catalog.updated"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type AgentUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "agent.updated"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type SnapshotFileDiffV2 = {
|
||||
file?: string
|
||||
patch?: string
|
||||
additions: number
|
||||
deletions: number
|
||||
status?: "added" | "deleted" | "modified"
|
||||
}
|
||||
|
||||
export type PermissionActionV2 = "allow" | "deny" | "ask"
|
||||
|
||||
export type PermissionRuleV2 = {
|
||||
permission: string
|
||||
pattern: string
|
||||
action: PermissionActionV2
|
||||
}
|
||||
|
||||
export type PermissionRulesetV2 = Array<PermissionRuleV2>
|
||||
|
||||
export type SessionV2 = {
|
||||
id: string
|
||||
slug: string
|
||||
@@ -9628,7 +8058,7 @@ export type SessionV2 = {
|
||||
additions: number
|
||||
deletions: number
|
||||
files: number
|
||||
diffs?: Array<SnapshotFileDiffV2>
|
||||
diffs?: Array<SnapshotFileDiff>
|
||||
}
|
||||
cost?: number
|
||||
tokens?: {
|
||||
@@ -9660,7 +8090,7 @@ export type SessionV2 = {
|
||||
compacting?: number
|
||||
archived?: number
|
||||
}
|
||||
permission?: PermissionRulesetV2
|
||||
permission?: PermissionRuleset
|
||||
revert?: {
|
||||
messageID: string
|
||||
partID?: string
|
||||
@@ -9669,74 +8099,13 @@ export type SessionV2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCreated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.created"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: SessionV2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: SessionV2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionDeleted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.deleted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: SessionV2
|
||||
}
|
||||
}
|
||||
|
||||
export type JsonSchemaV2 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type OutputFormatV2 =
|
||||
| {
|
||||
type: "text"
|
||||
}
|
||||
| {
|
||||
type: "json_schema"
|
||||
schema: JsonSchemaV2
|
||||
schema: JsonSchema
|
||||
retryCount?: number | null | null
|
||||
}
|
||||
|
||||
@@ -9751,7 +8120,7 @@ export type UserMessageV2 = {
|
||||
summary?: {
|
||||
title?: string | null
|
||||
body?: string | null
|
||||
diffs: Array<SnapshotFileDiffV2>
|
||||
diffs: Array<SnapshotFileDiff>
|
||||
} | null
|
||||
agent: string
|
||||
model: {
|
||||
@@ -9765,14 +8134,6 @@ export type UserMessageV2 = {
|
||||
} | null
|
||||
}
|
||||
|
||||
export type ProviderAuthErrorV2 = {
|
||||
name: "ProviderAuthError"
|
||||
data: {
|
||||
providerID: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type UnknownError1V2 = {
|
||||
name: "UnknownError"
|
||||
data: {
|
||||
@@ -9790,13 +8151,6 @@ export type MessageOutputLengthErrorV2 = {
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type MessageAbortedErrorV2 = {
|
||||
name: "MessageAbortedError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type StructuredOutputErrorV2 = {
|
||||
name: "StructuredOutputError"
|
||||
data: {
|
||||
@@ -9813,13 +8167,6 @@ export type ContextOverflowErrorV2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type ContentFilterErrorV2 = {
|
||||
name: "ContentFilterError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ApiErrorV2 = {
|
||||
name: "APIError"
|
||||
data: {
|
||||
@@ -9845,13 +8192,13 @@ export type AssistantMessageV2 = {
|
||||
completed?: number | null
|
||||
}
|
||||
error?:
|
||||
| ProviderAuthErrorV2
|
||||
| ProviderAuthError
|
||||
| UnknownError1V2
|
||||
| MessageOutputLengthErrorV2
|
||||
| MessageAbortedErrorV2
|
||||
| MessageAbortedError
|
||||
| StructuredOutputErrorV2
|
||||
| ContextOverflowErrorV2
|
||||
| ContentFilterErrorV2
|
||||
| ContentFilterError
|
||||
| ApiErrorV2
|
||||
| null
|
||||
parentID: string
|
||||
@@ -9880,46 +8227,6 @@ export type AssistantMessageV2 = {
|
||||
finish?: string | null
|
||||
}
|
||||
|
||||
export type MessageV2 = UserMessageV2 | AssistantMessageV2
|
||||
|
||||
export type MessageUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: MessageV2
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageRemoved2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.removed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TextPartV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
@@ -9967,18 +8274,6 @@ export type ReasoningPartV2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type FilePartSourceTextV2 = {
|
||||
value: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export type FileSourceV2 = {
|
||||
text: FilePartSourceTextV2
|
||||
type: "file"
|
||||
path: string
|
||||
}
|
||||
|
||||
export type RangeV2 = {
|
||||
start: {
|
||||
line: number
|
||||
@@ -9991,7 +8286,7 @@ export type RangeV2 = {
|
||||
}
|
||||
|
||||
export type SymbolSourceV2 = {
|
||||
text: FilePartSourceTextV2
|
||||
text: FilePartSourceText
|
||||
type: "symbol"
|
||||
path: string
|
||||
range: RangeV2
|
||||
@@ -9999,15 +8294,6 @@ export type SymbolSourceV2 = {
|
||||
kind: number
|
||||
}
|
||||
|
||||
export type ResourceSourceV2 = {
|
||||
text: FilePartSourceTextV2
|
||||
type: "resource"
|
||||
clientName: string
|
||||
uri: string
|
||||
}
|
||||
|
||||
export type FilePartSourceV2 = FileSourceV2 | SymbolSourceV2 | ResourceSourceV2
|
||||
|
||||
export type FilePartV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
@@ -10016,15 +8302,7 @@ export type FilePartV2 = {
|
||||
mime: string
|
||||
filename?: string | null
|
||||
url: string
|
||||
source?: FilePartSourceV2 | null
|
||||
}
|
||||
|
||||
export type ToolStatePendingV2 = {
|
||||
status: "pending"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
raw: string
|
||||
source?: FilePartSource | null
|
||||
}
|
||||
|
||||
export type ToolStateRunningV2 = {
|
||||
@@ -10074,8 +8352,6 @@ export type ToolStateErrorV2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolStateV2 = ToolStatePendingV2 | ToolStateRunningV2 | ToolStateCompletedV2 | ToolStateErrorV2
|
||||
|
||||
export type ToolPartV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
@@ -10083,7 +8359,7 @@ export type ToolPartV2 = {
|
||||
type: "tool"
|
||||
callID: string
|
||||
tool: string
|
||||
state: ToolStateV2
|
||||
state: ToolState
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
@@ -10169,273 +8445,6 @@ export type CompactionPartV2 = {
|
||||
tail_start_id?: string | null
|
||||
}
|
||||
|
||||
export type PartV2 =
|
||||
| TextPartV2
|
||||
| SubtaskPartV2
|
||||
| ReasoningPartV2
|
||||
| FilePartV2
|
||||
| ToolPartV2
|
||||
| StepStartPartV2
|
||||
| StepFinishPartV2
|
||||
| SnapshotPartV2
|
||||
| PatchPartV2
|
||||
| AgentPartV2
|
||||
| RetryPartV2
|
||||
| CompactionPartV2
|
||||
|
||||
export type MessagePartUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.part.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
part: PartV2
|
||||
time: number
|
||||
}
|
||||
}
|
||||
|
||||
export type MessagePartRemoved2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.part.removed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
partID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionSettled2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.settled"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
outcome: "success" | "failure" | "interrupted"
|
||||
error?: SessionErrorUnknown2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionTextDelta2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.text.delta"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
textID: string
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionReasoningDelta2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.reasoning.delta"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
reasoningID: string
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolInputDelta2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.input.delta"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionDelta2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.delta"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type FilesystemChanged2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "filesystem.changed"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
file: string
|
||||
event: "add" | "change" | "unlink"
|
||||
}
|
||||
}
|
||||
|
||||
export type ReferenceUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "reference.updated"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type PermissionV2Asked2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "permission.v2.asked"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
action: string
|
||||
resources: Array<string>
|
||||
save?: Array<string>
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2Source2
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionV2Replied2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "permission.v2.replied"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
reply: PermissionV2Reply2
|
||||
}
|
||||
}
|
||||
|
||||
export type PluginAdded2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "plugin.added"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ProjectDirectoriesUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "project.directories.updated"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
projectID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type CommandUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "command.updated"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type ConfigUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "config.updated"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type SkillUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "skill.updated"
|
||||
location?: LocationRef2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type PtyV2 = {
|
||||
id: string
|
||||
title: string
|
||||
@@ -10447,353 +8456,6 @@ export type PtyV2 = {
|
||||
exitCode?: number
|
||||
}
|
||||
|
||||
export type PtyCreated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.created"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
info: PtyV2
|
||||
}
|
||||
}
|
||||
|
||||
export type PtyUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.updated"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
info: PtyV2
|
||||
}
|
||||
}
|
||||
|
||||
export type PtyExited2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.exited"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
exitCode: number
|
||||
}
|
||||
}
|
||||
|
||||
export type PtyDeleted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.deleted"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellCreated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "shell.created"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
info: Shell1V2
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellExited2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "shell.exited"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
exit?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
status: "running" | "exited" | "timeout" | "killed"
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellDeleted2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "shell.deleted"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionV2Option2 = {
|
||||
/**
|
||||
* Display text (1-5 words, concise)
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* Explanation of choice
|
||||
*/
|
||||
description: string
|
||||
}
|
||||
|
||||
export type QuestionV2Info2 = {
|
||||
/**
|
||||
* Complete question
|
||||
*/
|
||||
question: string
|
||||
/**
|
||||
* Very short label (max 30 chars)
|
||||
*/
|
||||
header: string
|
||||
/**
|
||||
* Available choices
|
||||
*/
|
||||
options: Array<QuestionV2Option2>
|
||||
multiple?: boolean
|
||||
custom?: boolean
|
||||
}
|
||||
|
||||
export type QuestionV2Tool2 = {
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
|
||||
export type QuestionV2Asked2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.v2.asked"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
/**
|
||||
* Questions to ask
|
||||
*/
|
||||
questions: Array<QuestionV2Info2>
|
||||
tool?: QuestionV2Tool2
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionV2Answer2 = Array<string>
|
||||
|
||||
export type QuestionV2Replied2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.v2.replied"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
answers: Array<QuestionV2Answer2>
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionV2Rejected2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.v2.rejected"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type FormMetadata1 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type FormWhen12 = {
|
||||
key: string
|
||||
op: "eq" | "neq"
|
||||
value: string | number | "NaN" | "Infinity" | "-Infinity" | boolean
|
||||
}
|
||||
|
||||
export type FormStringField1 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen12>
|
||||
type: "string"
|
||||
format?: "email" | "uri" | "date" | "date-time"
|
||||
minLength?: number
|
||||
maxLength?: number
|
||||
pattern?: string
|
||||
placeholder?: string
|
||||
default?: string
|
||||
options?: Array<FormOption2>
|
||||
custom?: boolean
|
||||
}
|
||||
|
||||
export type FormNumberField12 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen12>
|
||||
type: "number"
|
||||
minimum?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
maximum?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
default?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
}
|
||||
|
||||
export type FormIntegerField12 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen12>
|
||||
type: "integer"
|
||||
minimum?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
maximum?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
default?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
}
|
||||
|
||||
export type FormBooleanField1 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen12>
|
||||
type: "boolean"
|
||||
default?: boolean
|
||||
}
|
||||
|
||||
export type FormMultiselectField1 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen12>
|
||||
type: "multiselect"
|
||||
options: Array<FormOption2>
|
||||
minItems?: number
|
||||
maxItems?: number
|
||||
custom?: boolean
|
||||
default?: Array<string>
|
||||
}
|
||||
|
||||
export type FormFormInfo1 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata1
|
||||
mode: "form"
|
||||
fields: Array<FormStringField1 | FormNumberField12 | FormIntegerField12 | FormBooleanField1 | FormMultiselectField1>
|
||||
}
|
||||
|
||||
export type FormUrlInfo1 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata1
|
||||
mode: "url"
|
||||
url: string
|
||||
}
|
||||
|
||||
export type FormCreated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "form.created"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
form: FormFormInfo1 | FormUrlInfo1
|
||||
}
|
||||
}
|
||||
|
||||
export type FormValue12 = string | number | "NaN" | "Infinity" | "-Infinity" | boolean | Array<string>
|
||||
|
||||
export type FormAnswer1 = {
|
||||
[key: string]: FormValue12
|
||||
}
|
||||
|
||||
export type FormReplied2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "form.replied"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
answer: FormAnswer1
|
||||
}
|
||||
}
|
||||
|
||||
export type FormCancelled2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "form.cancelled"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TodoV2 = {
|
||||
/**
|
||||
* Brief description of the task
|
||||
*/
|
||||
content: string
|
||||
/**
|
||||
* Current status of the task: pending, in_progress, completed, cancelled
|
||||
*/
|
||||
status: string
|
||||
/**
|
||||
* Priority level of the task: high, medium, low
|
||||
*/
|
||||
priority: string
|
||||
}
|
||||
|
||||
export type TodoUpdated2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "todo.updated"
|
||||
location?: LocationRef2
|
||||
data: {
|
||||
sessionID: string
|
||||
todos: Array<TodoV2>
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionStatusV2 =
|
||||
| {
|
||||
type: "idle"
|
||||
@@ -10823,47 +8485,1994 @@ export type SessionStatusV22 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.status"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
status: SessionStatusV2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionIdle2 = {
|
||||
export type QuestionInfoV2 = {
|
||||
/**
|
||||
* Complete question
|
||||
*/
|
||||
question: string
|
||||
/**
|
||||
* Very short label (max 30 chars)
|
||||
*/
|
||||
header: string
|
||||
/**
|
||||
* Available choices
|
||||
*/
|
||||
options: Array<QuestionOption>
|
||||
/**
|
||||
* Allow selecting multiple choices
|
||||
*/
|
||||
multiple?: boolean | null
|
||||
/**
|
||||
* Allow typing a custom answer (default: true)
|
||||
*/
|
||||
custom?: boolean | null
|
||||
}
|
||||
|
||||
export type QuestionToolV2 = {
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
|
||||
export type V2EventV2 =
|
||||
| ModelsDevRefreshedV2
|
||||
| IntegrationUpdatedV2
|
||||
| IntegrationConnectionUpdatedV2
|
||||
| CatalogUpdatedV2
|
||||
| AgentUpdatedV2
|
||||
| SessionCreatedV2
|
||||
| SessionUpdatedV2
|
||||
| SessionDeletedV2
|
||||
| MessageUpdatedV2
|
||||
| MessageRemovedV2
|
||||
| MessagePartUpdatedV2
|
||||
| MessagePartRemovedV2
|
||||
| SessionAgentSelectedV2
|
||||
| SessionModelSelectedV2
|
||||
| SessionMovedV2
|
||||
| SessionRenamedV2
|
||||
| SessionForkedV2
|
||||
| SessionPromptPromotedV2
|
||||
| SessionPromptAdmittedV2
|
||||
| SessionExecutionStartedV2
|
||||
| SessionExecutionSucceededV2
|
||||
| SessionExecutionFailedV2
|
||||
| SessionExecutionInterruptedV2
|
||||
| SessionContextUpdatedV2
|
||||
| SessionSyntheticV2
|
||||
| SessionSkillActivatedV2
|
||||
| SessionShellStartedV2
|
||||
| SessionShellEndedV2
|
||||
| SessionStepStartedV2
|
||||
| SessionStepEndedV2
|
||||
| SessionStepFailedV2
|
||||
| SessionTextStartedV2
|
||||
| SessionTextDeltaV2
|
||||
| SessionTextEndedV2
|
||||
| SessionReasoningStartedV2
|
||||
| SessionReasoningDeltaV2
|
||||
| SessionReasoningEndedV2
|
||||
| SessionToolInputStartedV2
|
||||
| SessionToolInputDeltaV2
|
||||
| SessionToolInputEndedV2
|
||||
| SessionToolCalledV2
|
||||
| SessionToolProgressV2
|
||||
| SessionToolSuccessV2
|
||||
| SessionToolFailedV2
|
||||
| SessionRetryScheduledV2
|
||||
| SessionCompactionAdmittedV2
|
||||
| SessionCompactionStartedV2
|
||||
| SessionCompactionDeltaV2
|
||||
| SessionCompactionEndedV2
|
||||
| SessionCompactionFailedV2
|
||||
| SessionRevertStagedV2
|
||||
| SessionRevertClearedV2
|
||||
| SessionRevertCommittedV2
|
||||
| FilesystemChangedV2
|
||||
| ReferenceUpdatedV2
|
||||
| PermissionV2AskedV2
|
||||
| PermissionV2RepliedV2
|
||||
| PluginAddedV2
|
||||
| ProjectDirectoriesUpdatedV2
|
||||
| CommandUpdatedV2
|
||||
| ConfigUpdatedV2
|
||||
| SkillUpdatedV2
|
||||
| PtyCreatedV2
|
||||
| PtyUpdatedV2
|
||||
| PtyExitedV2
|
||||
| PtyDeletedV2
|
||||
| ShellCreatedV2
|
||||
| ShellExitedV2
|
||||
| ShellDeletedV2
|
||||
| QuestionV2AskedV2
|
||||
| QuestionV2RepliedV2
|
||||
| QuestionV2RejectedV2
|
||||
| FormCreatedV2
|
||||
| FormRepliedV2
|
||||
| FormCancelledV2
|
||||
| TodoUpdatedV2
|
||||
| SessionStatusV22
|
||||
| SessionIdleV2
|
||||
| TuiPromptAppendV2
|
||||
| TuiCommandExecuteV2
|
||||
| TuiToastShowV2
|
||||
| TuiSessionSelectV2
|
||||
| InstallationUpdatedV2
|
||||
| InstallationUpdateAvailableV2
|
||||
| VcsBranchUpdatedV2
|
||||
| McpStatusChangedV2
|
||||
| PermissionAskedV2
|
||||
| PermissionRepliedV2
|
||||
| QuestionAskedV2
|
||||
| QuestionRepliedV2
|
||||
| QuestionRejectedV2
|
||||
| SessionErrorV2
|
||||
| V2EventServerConnected
|
||||
|
||||
export type ProjectCopyErrorV2 = {
|
||||
name: "ProjectCopyError"
|
||||
data: {
|
||||
message: string
|
||||
forceRequired?: boolean | null
|
||||
}
|
||||
}
|
||||
|
||||
export type LocationInfoV2 = {
|
||||
directory: string
|
||||
workspaceID?: string
|
||||
project: {
|
||||
id: string
|
||||
directory: string
|
||||
}
|
||||
}
|
||||
|
||||
export type AgentColorV2 = string | "primary" | "secondary" | "accent" | "success" | "warning" | "error" | "info"
|
||||
|
||||
export type AgentV2InfoV2 = {
|
||||
id: string
|
||||
model?: ModelRef
|
||||
request: ProviderRequest
|
||||
system?: string
|
||||
description?: string
|
||||
mode: "subagent" | "primary" | "all"
|
||||
hidden: boolean
|
||||
color?: AgentColorV2
|
||||
steps?: number
|
||||
permissions: PermissionV2Ruleset
|
||||
}
|
||||
|
||||
export type LocationRefV2 = {
|
||||
directory: string
|
||||
workspaceID?: string
|
||||
}
|
||||
|
||||
export type FileDiffV2 = {
|
||||
path: string
|
||||
status: "added" | "modified" | "deleted"
|
||||
additions: number
|
||||
deletions: number
|
||||
patch: string
|
||||
}
|
||||
|
||||
export type RevertStateV2 = {
|
||||
messageID: string
|
||||
partID?: string
|
||||
snapshot?: string
|
||||
diff?: string
|
||||
files?: Array<FileDiffV2>
|
||||
}
|
||||
|
||||
export type SessionV2InfoV2 = {
|
||||
id: string
|
||||
parentID?: string
|
||||
projectID: string
|
||||
agent?: string
|
||||
model?: ModelRef
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
updated: number
|
||||
archived?: number
|
||||
}
|
||||
title: string
|
||||
location: LocationRefV2
|
||||
subpath?: string
|
||||
revert?: RevertStateV2
|
||||
}
|
||||
|
||||
export type SessionInputAdmittedV2 = {
|
||||
admittedSeq: number
|
||||
id: string
|
||||
sessionID: string
|
||||
prompt: Prompt
|
||||
delivery: "steer" | "queue"
|
||||
timeCreated: number
|
||||
promotedSeq?: number
|
||||
}
|
||||
|
||||
export type SessionInputCompactionV2 = {
|
||||
type: "compaction"
|
||||
admittedSeq: number
|
||||
id: string
|
||||
sessionID: string
|
||||
timeCreated: number
|
||||
handledSeq?: number
|
||||
}
|
||||
|
||||
export type SessionMessageAgentSelectedV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "agent-switched"
|
||||
agent: string
|
||||
}
|
||||
|
||||
export type SessionMessageModelSelectedV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "model-switched"
|
||||
model: ModelRef
|
||||
previous?: ModelRef
|
||||
}
|
||||
|
||||
export type SessionMessageUserV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
text: string
|
||||
files?: Array<PromptFileAttachment>
|
||||
agents?: Array<PromptAgentAttachment>
|
||||
type: "user"
|
||||
}
|
||||
|
||||
export type SessionMessageSyntheticV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
sessionID: string
|
||||
text: string
|
||||
description?: string
|
||||
type: "synthetic"
|
||||
}
|
||||
|
||||
export type SessionMessageSystemV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "system"
|
||||
text: string
|
||||
}
|
||||
|
||||
export type SessionMessageSkillV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
type: "skill"
|
||||
name: string
|
||||
text: string
|
||||
}
|
||||
|
||||
export type SessionMessageShellV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
type: "shell"
|
||||
shell: ShellV2
|
||||
output?: {
|
||||
output: string
|
||||
cursor: number
|
||||
size: number
|
||||
truncated: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantRetryV2 = {
|
||||
attempt: number
|
||||
at: number
|
||||
error: SessionStructuredError
|
||||
}
|
||||
|
||||
export type SessionMessageAssistantV2 = {
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
type: "assistant"
|
||||
agent: string
|
||||
model: ModelRef
|
||||
content: Array<SessionMessageAssistantText | SessionMessageAssistantReasoning | SessionMessageAssistantTool>
|
||||
snapshot?: {
|
||||
start?: string
|
||||
end?: string
|
||||
files?: Array<string>
|
||||
}
|
||||
finish?: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
cost?: number
|
||||
tokens?: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
error?: SessionStructuredError
|
||||
retry?: SessionMessageAssistantRetryV2
|
||||
}
|
||||
|
||||
export type SessionMessageCompactionV2 = {
|
||||
type: "compaction"
|
||||
status: "queued" | "running" | "completed" | "failed"
|
||||
reason: "auto" | "manual"
|
||||
summary: string
|
||||
recent: string
|
||||
id: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Context entry key (lowercase alphanumerics plus . _ -)
|
||||
*/
|
||||
export type SessionContextEntryKeyV2 = string
|
||||
|
||||
export type SessionAgentSelectedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.agent.selected"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
agent: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionModelSelectedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.model.selected"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
model: ModelRef
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMovedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.moved"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
location: LocationRefV2
|
||||
subpath?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRenamedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.renamed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
title: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionForkedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.forked"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
parentID: string
|
||||
from?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionPromptPromotedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.prompt.promoted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionPromptAdmittedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.prompt.admitted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
prompt: Prompt
|
||||
delivery: "steer" | "queue"
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionStartedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionSucceededV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.succeeded"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionFailedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionExecutionInterruptedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.execution.interrupted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
reason: "user" | "shutdown" | "superseded"
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionContextUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.context.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionSyntheticV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.synthetic"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
text: string
|
||||
description?: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionSkillActivatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.skill.activated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
name: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionShellStartedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.shell.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
shell: ShellV2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionShellEndedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.shell.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
shell: ShellV2
|
||||
output: {
|
||||
output: string
|
||||
cursor: number
|
||||
size: number
|
||||
truncated: boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionStepStartedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.step.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
agent: string
|
||||
model: ModelRef
|
||||
snapshot?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionStepEndedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.step.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
finish: "stop" | "length" | "tool-calls" | "content-filter" | "error" | "unknown"
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
snapshot?: string
|
||||
files?: Array<string>
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionStepFailedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.step.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionTextStartedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.text.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionTextEndedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.text.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageProviderState3 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SessionReasoningStartedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.reasoning.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
state?: SessionMessageProviderState3
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageProviderState4 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SessionReasoningEndedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.reasoning.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
text: string
|
||||
state?: SessionMessageProviderState4
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolInputStartedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.input.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
name: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolInputEndedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.input.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageProviderState5 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SessionToolCalledV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.called"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
executed: boolean
|
||||
state?: SessionMessageProviderState5
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolProgressV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.progress"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
content: Array<LlmToolContent>
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageProviderState6 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SessionToolSuccessV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.success"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
structured: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
content: Array<LlmToolContent>
|
||||
outputPaths?: Array<string>
|
||||
result?: unknown
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState6
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionMessageProviderState7 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type SessionToolFailedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
error: SessionStructuredError
|
||||
result?: unknown
|
||||
executed: boolean
|
||||
resultState?: SessionMessageProviderState7
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRetryScheduledV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.retry.scheduled"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
attempt: number
|
||||
at: number
|
||||
error: SessionStructuredError
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionAdmittedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.admitted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
inputID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionStartedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.started"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
reason: "auto" | "manual"
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionEndedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.ended"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
reason: "auto" | "manual"
|
||||
text: string
|
||||
recent: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionFailedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.failed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRevertStagedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.revert.staged"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
revert: RevertStateV2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRevertClearedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.revert.cleared"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionRevertCommittedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.revert.committed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
to: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq.
|
||||
*/
|
||||
export type EventLogSyncedV2 = {
|
||||
type: "log.synced"
|
||||
aggregateID: string
|
||||
seq?: number
|
||||
}
|
||||
|
||||
export type FormWhenV2 = {
|
||||
key: string
|
||||
op: "eq" | "neq"
|
||||
value: string | number | "NaN" | "Infinity" | "-Infinity" | "Infinity" | "-Infinity" | "NaN" | boolean
|
||||
}
|
||||
|
||||
export type FormStringFieldV2 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhenV2>
|
||||
type: "string"
|
||||
format?: "email" | "uri" | "date" | "date-time"
|
||||
minLength?: number
|
||||
maxLength?: number
|
||||
pattern?: string
|
||||
placeholder?: string
|
||||
default?: string
|
||||
options?: Array<FormOption>
|
||||
custom?: boolean
|
||||
}
|
||||
|
||||
export type FormMultiselectFieldV2 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhenV2>
|
||||
type: "multiselect"
|
||||
options: Array<FormOption>
|
||||
minItems?: number
|
||||
maxItems?: number
|
||||
custom?: boolean
|
||||
default?: Array<string>
|
||||
}
|
||||
|
||||
export type FormFormInfoV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata
|
||||
mode: "form"
|
||||
fields: Array<FormStringFieldV2 | FormNumberField | FormIntegerField | FormBooleanField | FormMultiselectFieldV2>
|
||||
}
|
||||
|
||||
export type FormUrlInfoV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata
|
||||
mode: "url"
|
||||
url: string
|
||||
}
|
||||
|
||||
export type FormCreatePayloadV2 = {
|
||||
id?: string | null
|
||||
title?: string
|
||||
metadata?: FormMetadata
|
||||
mode: "form" | "url"
|
||||
fields?: Array<
|
||||
FormStringFieldV2 | FormNumberField | FormIntegerField | FormBooleanField | FormMultiselectFieldV2
|
||||
> | null
|
||||
url?: string | null
|
||||
}
|
||||
|
||||
export type FormValueV2 =
|
||||
| string
|
||||
| number
|
||||
| "NaN"
|
||||
| "Infinity"
|
||||
| "-Infinity"
|
||||
| "Infinity"
|
||||
| "-Infinity"
|
||||
| "NaN"
|
||||
| boolean
|
||||
| Array<string>
|
||||
|
||||
export type PermissionV2SourceV2 = {
|
||||
type: "tool"
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
|
||||
export type PermissionV2RequestV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
action: string
|
||||
resources: Array<string>
|
||||
save?: Array<string>
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2SourceV2
|
||||
}
|
||||
|
||||
export type ModelsDevRefreshedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "models-dev.refreshed"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type IntegrationUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "integration.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type IntegrationConnectionUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "integration.connection.updated"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
integrationID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type CatalogUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "catalog.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type AgentUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "agent.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type SessionCreatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.created"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: SessionV2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: SessionV2
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionDeletedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.deleted"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: SessionV2
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
info: Message
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageRemovedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.removed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type MessagePartUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.part.updated"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
part: Part
|
||||
time: number
|
||||
}
|
||||
}
|
||||
|
||||
export type MessagePartRemovedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "message.part.removed"
|
||||
durable: {
|
||||
aggregateID: string
|
||||
seq: number
|
||||
version: number
|
||||
}
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
partID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionTextDeltaV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.text.delta"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionReasoningDeltaV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.reasoning.delta"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
ordinal: number
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionToolInputDeltaV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.tool.input.delta"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
assistantMessageID: string
|
||||
callID: string
|
||||
delta: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionCompactionDeltaV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.compaction.delta"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type FilesystemChangedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "filesystem.changed"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
file: string
|
||||
event: "add" | "change" | "unlink"
|
||||
}
|
||||
}
|
||||
|
||||
export type ReferenceUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "reference.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type PermissionV2AskedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "permission.v2.asked"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
action: string
|
||||
resources: Array<string>
|
||||
save?: Array<string>
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2SourceV2
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionV2RepliedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "permission.v2.replied"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
reply: PermissionV2Reply
|
||||
}
|
||||
}
|
||||
|
||||
export type PluginAddedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "plugin.added"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ProjectDirectoriesUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "project.directories.updated"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
projectID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type CommandUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "command.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type ConfigUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "config.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type SkillUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "skill.updated"
|
||||
location?: LocationRefV2
|
||||
data:
|
||||
| {
|
||||
[key: string]: unknown
|
||||
}
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type PtyCreatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.created"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
info: PtyV2
|
||||
}
|
||||
}
|
||||
|
||||
export type PtyUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.updated"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
info: PtyV2
|
||||
}
|
||||
}
|
||||
|
||||
export type PtyExitedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.exited"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
exitCode: number
|
||||
}
|
||||
}
|
||||
|
||||
export type PtyDeletedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "pty.deleted"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellCreatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "shell.created"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
info: ShellV2
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellExitedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "shell.exited"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
exit?: number | "NaN" | "Infinity" | "-Infinity"
|
||||
status: "running" | "exited" | "timeout" | "killed"
|
||||
}
|
||||
}
|
||||
|
||||
export type ShellDeletedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "shell.deleted"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionV2AskedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.v2.asked"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
/**
|
||||
* Questions to ask
|
||||
*/
|
||||
questions: Array<QuestionV2Info>
|
||||
tool?: QuestionV2Tool
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionV2RepliedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.v2.replied"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
answers: Array<QuestionV2Answer>
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionV2RejectedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.v2.rejected"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type FormMetadata1 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export type FormStringField1 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen1>
|
||||
type: "string"
|
||||
format?: "email" | "uri" | "date" | "date-time"
|
||||
minLength?: number
|
||||
maxLength?: number
|
||||
pattern?: string
|
||||
placeholder?: string
|
||||
default?: string
|
||||
options?: Array<FormOption>
|
||||
custom?: boolean
|
||||
}
|
||||
|
||||
export type FormBooleanField1 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen1>
|
||||
type: "boolean"
|
||||
default?: boolean
|
||||
}
|
||||
|
||||
export type FormMultiselectField1 = {
|
||||
key: string
|
||||
title?: string
|
||||
description?: string
|
||||
required?: boolean
|
||||
when?: Array<FormWhen1>
|
||||
type: "multiselect"
|
||||
options: Array<FormOption>
|
||||
minItems?: number
|
||||
maxItems?: number
|
||||
custom?: boolean
|
||||
default?: Array<string>
|
||||
}
|
||||
|
||||
export type FormFormInfo1 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata1
|
||||
mode: "form"
|
||||
fields: Array<FormStringField1 | FormNumberField1 | FormIntegerField1 | FormBooleanField1 | FormMultiselectField1>
|
||||
}
|
||||
|
||||
export type FormUrlInfo1 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
title?: string
|
||||
metadata?: FormMetadata1
|
||||
mode: "url"
|
||||
url: string
|
||||
}
|
||||
|
||||
export type FormCreatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "form.created"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
form: FormFormInfo1 | FormUrlInfo1
|
||||
}
|
||||
}
|
||||
|
||||
export type FormAnswer1 = {
|
||||
[key: string]: FormValue1
|
||||
}
|
||||
|
||||
export type FormRepliedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "form.replied"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
answer: FormAnswer1
|
||||
}
|
||||
}
|
||||
|
||||
export type FormCancelledV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "form.cancelled"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TodoUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "todo.updated"
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
todos: Array<Todo>
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionIdleV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.idle"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TuiPromptAppend2 = {
|
||||
export type TuiPromptAppendV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "tui.prompt.append"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
text: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TuiCommandExecute2 = {
|
||||
export type TuiCommandExecuteV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "tui.command.execute"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
command:
|
||||
| "session.list"
|
||||
@@ -10887,14 +10496,14 @@ export type TuiCommandExecute2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type TuiToastShow2 = {
|
||||
export type TuiToastShowV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "tui.toast.show"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
title?: string
|
||||
message: string
|
||||
@@ -10903,14 +10512,14 @@ export type TuiToastShow2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type TuiSessionSelect2 = {
|
||||
export type TuiSessionSelectV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "tui.session.select"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
/**
|
||||
* Session ID to navigate to
|
||||
@@ -10919,66 +10528,66 @@ export type TuiSessionSelect2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type InstallationUpdated2 = {
|
||||
export type InstallationUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "installation.updated"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type InstallationUpdateAvailable2 = {
|
||||
export type InstallationUpdateAvailableV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "installation.update-available"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type VcsBranchUpdated2 = {
|
||||
export type VcsBranchUpdatedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "vcs.branch.updated"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
branch?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type McpStatusChanged2 = {
|
||||
export type McpStatusChangedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "mcp.status.changed"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
server: string
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionAsked2 = {
|
||||
export type PermissionAskedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "permission.asked"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
@@ -10995,14 +10604,14 @@ export type PermissionAsked2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type PermissionReplied2 = {
|
||||
export type PermissionRepliedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "permission.replied"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
@@ -11010,53 +10619,14 @@ export type PermissionReplied2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionOptionV2 = {
|
||||
/**
|
||||
* Display text (1-5 words, concise)
|
||||
*/
|
||||
label: string
|
||||
/**
|
||||
* Explanation of choice
|
||||
*/
|
||||
description: string
|
||||
}
|
||||
|
||||
export type QuestionInfoV2 = {
|
||||
/**
|
||||
* Complete question
|
||||
*/
|
||||
question: string
|
||||
/**
|
||||
* Very short label (max 30 chars)
|
||||
*/
|
||||
header: string
|
||||
/**
|
||||
* Available choices
|
||||
*/
|
||||
options: Array<QuestionOptionV2>
|
||||
/**
|
||||
* Allow selecting multiple choices
|
||||
*/
|
||||
multiple?: boolean | null
|
||||
/**
|
||||
* Allow typing a custom answer (default: true)
|
||||
*/
|
||||
custom?: boolean | null
|
||||
}
|
||||
|
||||
export type QuestionToolV2 = {
|
||||
messageID: string
|
||||
callID: string
|
||||
}
|
||||
|
||||
export type QuestionAsked2 = {
|
||||
export type QuestionAskedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.asked"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
id: string
|
||||
sessionID: string
|
||||
@@ -11068,8 +10638,6 @@ export type QuestionAsked2 = {
|
||||
}
|
||||
}
|
||||
|
||||
export type QuestionAnswerV2 = Array<string>
|
||||
|
||||
export type QuestionRepliedV2 = {
|
||||
id: string
|
||||
created: number
|
||||
@@ -11077,11 +10645,11 @@ export type QuestionRepliedV2 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.replied"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
answers: Array<QuestionAnswerV2>
|
||||
answers: Array<QuestionAnswer>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11092,31 +10660,31 @@ export type QuestionRejectedV2 = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "question.rejected"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionError2 = {
|
||||
export type SessionErrorV2 = {
|
||||
id: string
|
||||
created: number
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type: "session.error"
|
||||
location?: LocationRef2
|
||||
location?: LocationRefV2
|
||||
data: {
|
||||
sessionID?: string | null
|
||||
error?:
|
||||
| ProviderAuthErrorV2
|
||||
| ProviderAuthError
|
||||
| UnknownError1V2
|
||||
| MessageOutputLengthErrorV2
|
||||
| MessageAbortedErrorV2
|
||||
| MessageAbortedError
|
||||
| StructuredOutputErrorV2
|
||||
| ContextOverflowErrorV2
|
||||
| ContentFilterErrorV2
|
||||
| ContentFilterError
|
||||
| ApiErrorV2
|
||||
| null
|
||||
}
|
||||
@@ -11127,7 +10695,7 @@ export type V2EventServerConnected = {
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
} | null
|
||||
location?: LocationRef2 | null
|
||||
location?: LocationRefV2 | null
|
||||
type: "server.connected"
|
||||
data:
|
||||
| {
|
||||
@@ -11136,102 +10704,10 @@ export type V2EventServerConnected = {
|
||||
| Array<unknown>
|
||||
}
|
||||
|
||||
export type V2EventV2 =
|
||||
| ModelsDevRefreshed2
|
||||
| IntegrationUpdated2
|
||||
| IntegrationConnectionUpdated2
|
||||
| CatalogUpdated2
|
||||
| AgentUpdated2
|
||||
| SessionCreated2
|
||||
| SessionUpdated2
|
||||
| SessionDeleted2
|
||||
| MessageUpdated2
|
||||
| MessageRemoved2
|
||||
| MessagePartUpdated2
|
||||
| MessagePartRemoved2
|
||||
| SessionAgentSelected2
|
||||
| SessionModelSelected2
|
||||
| SessionMoved2
|
||||
| SessionRenamed2
|
||||
| SessionForked2
|
||||
| SessionPromptPromoted2
|
||||
| SessionPromptAdmitted2
|
||||
| SessionExecutionSettled2
|
||||
| SessionContextUpdated2
|
||||
| SessionSynthetic2
|
||||
| SessionSkillActivated2
|
||||
| SessionShellStarted2
|
||||
| SessionShellEnded2
|
||||
| SessionStepStarted2
|
||||
| SessionStepEnded2
|
||||
| SessionStepFailed2
|
||||
| SessionTextStarted2
|
||||
| SessionTextDelta2
|
||||
| SessionTextEnded2
|
||||
| SessionReasoningStarted2
|
||||
| SessionReasoningDelta2
|
||||
| SessionReasoningEnded2
|
||||
| SessionToolInputStarted2
|
||||
| SessionToolInputDelta2
|
||||
| SessionToolInputEnded2
|
||||
| SessionToolCalled2
|
||||
| SessionToolProgress2
|
||||
| SessionToolSuccess2
|
||||
| SessionToolFailed2
|
||||
| SessionRetried2
|
||||
| SessionCompactionStarted2
|
||||
| SessionCompactionDelta2
|
||||
| SessionCompactionEnded2
|
||||
| SessionRevertStaged2
|
||||
| SessionRevertCleared2
|
||||
| SessionRevertCommitted2
|
||||
| FilesystemChanged2
|
||||
| ReferenceUpdated2
|
||||
| PermissionV2Asked2
|
||||
| PermissionV2Replied2
|
||||
| PluginAdded2
|
||||
| ProjectDirectoriesUpdated2
|
||||
| CommandUpdated2
|
||||
| ConfigUpdated2
|
||||
| SkillUpdated2
|
||||
| PtyCreated2
|
||||
| PtyUpdated2
|
||||
| PtyExited2
|
||||
| PtyDeleted2
|
||||
| ShellCreated2
|
||||
| ShellExited2
|
||||
| ShellDeleted2
|
||||
| QuestionV2Asked2
|
||||
| QuestionV2Replied2
|
||||
| QuestionV2Rejected2
|
||||
| FormCreated2
|
||||
| FormReplied2
|
||||
| FormCancelled2
|
||||
| TodoUpdated2
|
||||
| SessionStatusV22
|
||||
| SessionIdle2
|
||||
| TuiPromptAppend2
|
||||
| TuiCommandExecute2
|
||||
| TuiToastShow2
|
||||
| TuiSessionSelect2
|
||||
| InstallationUpdated2
|
||||
| InstallationUpdateAvailable2
|
||||
| VcsBranchUpdated2
|
||||
| McpStatusChanged2
|
||||
| PermissionAsked2
|
||||
| PermissionReplied2
|
||||
| QuestionAsked2
|
||||
| QuestionRepliedV2
|
||||
| QuestionRejectedV2
|
||||
| SessionError2
|
||||
| V2EventServerConnected
|
||||
|
||||
export type V2EventStreamV2 = string
|
||||
|
||||
/**
|
||||
* Payload-free change hint: the aggregate's durable log advanced to at least seq. Hints coalesce under backpressure (latest per aggregate) and are never a delivery guarantee.
|
||||
*/
|
||||
export type EventLogHint2 = {
|
||||
export type EventLogHintV2 = {
|
||||
type: "log.hint"
|
||||
aggregateID: string
|
||||
seq: number
|
||||
@@ -11240,94 +10716,23 @@ export type EventLogHint2 = {
|
||||
/**
|
||||
* Hints may have been lost; treat every aggregate as potentially dirty and recover via bounded sweep plus durable log reads. Emitted first on every (re)subscribe.
|
||||
*/
|
||||
export type EventLogSweepRequired2 = {
|
||||
export type EventLogSweepRequiredV2 = {
|
||||
type: "log.sweep_required"
|
||||
}
|
||||
|
||||
export type EventLogChange2 = EventLogHint2 | EventLogSweepRequired2
|
||||
|
||||
export type EventLogChangeStream2 = string
|
||||
|
||||
export type PtyNotFoundErrorV2 = {
|
||||
_tag: "PtyNotFoundError"
|
||||
ptyID: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type PtyTicketConnectToken2 = {
|
||||
export type PtyTicketConnectTokenV2 = {
|
||||
ticket: string
|
||||
expires_in: number
|
||||
}
|
||||
|
||||
export type ForbiddenErrorV2 = {
|
||||
_tag: "ForbiddenError"
|
||||
message: string
|
||||
}
|
||||
|
||||
export type ShellNotFoundErrorV2 = {
|
||||
_tag: "ShellNotFoundError"
|
||||
id: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type QuestionV2Request2 = {
|
||||
export type QuestionV2RequestV2 = {
|
||||
id: string
|
||||
sessionID: string
|
||||
/**
|
||||
* Questions to ask
|
||||
*/
|
||||
questions: Array<QuestionV2Info2>
|
||||
tool?: QuestionV2Tool2
|
||||
}
|
||||
|
||||
export type QuestionV2Reply2 = {
|
||||
/**
|
||||
* User answers in order of questions (each answer is an array of selected labels)
|
||||
*/
|
||||
answers: Array<QuestionV2Answer2>
|
||||
}
|
||||
|
||||
export type QuestionNotFoundErrorV2 = {
|
||||
_tag: "QuestionNotFoundError"
|
||||
requestID: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export type ReferenceLocalSource2 = {
|
||||
type: "local"
|
||||
path: string
|
||||
description?: string
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type ReferenceGitSource2 = {
|
||||
type: "git"
|
||||
repository: string
|
||||
branch?: string
|
||||
description?: string
|
||||
hidden?: boolean
|
||||
}
|
||||
|
||||
export type ReferenceSource2 = ReferenceLocalSource2 | ReferenceGitSource2
|
||||
|
||||
export type ReferenceInfo2 = {
|
||||
name: string
|
||||
path: string
|
||||
description?: string
|
||||
hidden?: boolean
|
||||
source: ReferenceSource2
|
||||
}
|
||||
|
||||
export type ProjectCopyCopy2 = {
|
||||
directory: string
|
||||
}
|
||||
|
||||
export type ProjectCopyErrorV2 = {
|
||||
name: "ProjectCopyError"
|
||||
data: {
|
||||
message: string
|
||||
forceRequired?: boolean | null
|
||||
}
|
||||
questions: Array<QuestionV2Info>
|
||||
tool?: QuestionV2Tool
|
||||
}
|
||||
|
||||
export type VcsFileStatusV2 = {
|
||||
@@ -11337,8 +10742,6 @@ export type VcsFileStatusV2 = {
|
||||
status: "added" | "deleted" | "modified"
|
||||
}
|
||||
|
||||
export type VcsMode2 = "working" | "branch"
|
||||
|
||||
export type AuthRemoveData = {
|
||||
body?: never
|
||||
path: {
|
||||
@@ -15487,7 +14890,7 @@ export type V2HealthGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2HealthGetError = V2HealthGetErrors[keyof V2HealthGetErrors]
|
||||
@@ -15523,7 +14926,7 @@ export type V2LocationGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2LocationGetError = V2LocationGetErrors[keyof V2LocationGetErrors]
|
||||
@@ -15532,7 +14935,7 @@ export type V2LocationGetResponses = {
|
||||
/**
|
||||
* Location.Info
|
||||
*/
|
||||
200: LocationInfo2
|
||||
200: LocationInfoV2
|
||||
}
|
||||
|
||||
export type V2LocationGetResponse = V2LocationGetResponses[keyof V2LocationGetResponses]
|
||||
@@ -15557,7 +14960,7 @@ export type V2AgentListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2AgentListError = V2AgentListErrors[keyof V2AgentListErrors]
|
||||
@@ -15567,8 +14970,8 @@ export type V2AgentListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<AgentV2Info2>
|
||||
location: LocationInfoV2
|
||||
data: Array<AgentV2InfoV2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15594,7 +14997,7 @@ export type V2PluginListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2PluginListError = V2PluginListErrors[keyof V2PluginListErrors]
|
||||
@@ -15604,8 +15007,8 @@ export type V2PluginListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<PluginInfo2>
|
||||
location: LocationInfoV2
|
||||
data: Array<PluginInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15638,11 +15041,11 @@ export type V2SessionListErrors = {
|
||||
/**
|
||||
* InvalidCursorError | InvalidRequestError
|
||||
*/
|
||||
400: InvalidCursorErrorV2 | InvalidRequestError1 | InvalidRequestErrorV2
|
||||
400: InvalidCursorError | InvalidRequestError1 | InvalidRequestErrorV2
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2SessionListError = V2SessionListErrors[keyof V2SessionListErrors]
|
||||
@@ -15660,8 +15063,8 @@ export type V2SessionCreateData = {
|
||||
body: {
|
||||
id?: string | null
|
||||
agent?: string | null
|
||||
model?: ModelRef2 | null
|
||||
location?: LocationRef2 | null
|
||||
model?: ModelRef | null
|
||||
location?: LocationRefV2 | null
|
||||
}
|
||||
path?: never
|
||||
query?: never
|
||||
@@ -15676,7 +15079,7 @@ export type V2SessionCreateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2SessionCreateError = V2SessionCreateErrors[keyof V2SessionCreateErrors]
|
||||
@@ -15686,7 +15089,7 @@ export type V2SessionCreateResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: SessionV2Info2
|
||||
data: SessionV2InfoV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15707,7 +15110,7 @@ export type V2SessionActiveErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2SessionActiveError = V2SessionActiveErrors[keyof V2SessionActiveErrors]
|
||||
@@ -15718,7 +15121,7 @@ export type V2SessionActiveResponses = {
|
||||
*/
|
||||
200: {
|
||||
data: {
|
||||
[key: string]: unknown | SessionActiveV2
|
||||
[key: string]: unknown | SessionActive
|
||||
}
|
||||
watermarks: SessionWatermarksV2
|
||||
}
|
||||
@@ -15743,11 +15146,11 @@ export type V2SessionGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionGetError = V2SessionGetErrors[keyof V2SessionGetErrors]
|
||||
@@ -15757,7 +15160,7 @@ export type V2SessionGetResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: SessionV2Info2
|
||||
data: SessionV2InfoV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15782,11 +15185,11 @@ export type V2SessionForkErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | MessageNotFoundError
|
||||
*/
|
||||
404: MessageNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: MessageNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionForkError = V2SessionForkErrors[keyof V2SessionForkErrors]
|
||||
@@ -15796,7 +15199,7 @@ export type V2SessionForkResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: SessionV2Info2
|
||||
data: SessionV2InfoV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15821,11 +15224,11 @@ export type V2SessionSwitchAgentErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionSwitchAgentError = V2SessionSwitchAgentErrors[keyof V2SessionSwitchAgentErrors]
|
||||
@@ -15841,7 +15244,7 @@ export type V2SessionSwitchAgentResponse = V2SessionSwitchAgentResponses[keyof V
|
||||
|
||||
export type V2SessionSwitchModelData = {
|
||||
body: {
|
||||
model: ModelRef2
|
||||
model: ModelRef
|
||||
}
|
||||
path: {
|
||||
sessionID: string
|
||||
@@ -15858,11 +15261,11 @@ export type V2SessionSwitchModelErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionSwitchModelError = V2SessionSwitchModelErrors[keyof V2SessionSwitchModelErrors]
|
||||
@@ -15895,11 +15298,11 @@ export type V2SessionRenameErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionRenameError = V2SessionRenameErrors[keyof V2SessionRenameErrors]
|
||||
@@ -15916,7 +15319,7 @@ export type V2SessionRenameResponse = V2SessionRenameResponses[keyof V2SessionRe
|
||||
export type V2SessionPromptData = {
|
||||
body: {
|
||||
id?: string | null
|
||||
prompt: PromptInputV2
|
||||
prompt: PromptInput
|
||||
delivery?: "steer" | "queue" | null
|
||||
resume?: boolean | null
|
||||
}
|
||||
@@ -15935,11 +15338,11 @@ export type V2SessionPromptErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* ConflictError
|
||||
*/
|
||||
@@ -15953,7 +15356,7 @@ export type V2SessionPromptResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: SessionInputAdmitted2
|
||||
data: SessionInputAdmittedV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15965,9 +15368,9 @@ export type V2SessionCommandData = {
|
||||
command: string
|
||||
arguments?: string | null
|
||||
agent?: string | null
|
||||
model?: ModelRef2 | null
|
||||
files?: Array<PromptInputFileAttachment2>
|
||||
agents?: Array<PromptAgentAttachment2>
|
||||
model?: ModelRef | null
|
||||
files?: Array<PromptInputFileAttachment>
|
||||
agents?: Array<PromptAgentAttachment>
|
||||
delivery?: "steer" | "queue" | null
|
||||
resume?: boolean | null
|
||||
}
|
||||
@@ -15986,11 +15389,11 @@ export type V2SessionCommandErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | CommandNotFoundError
|
||||
*/
|
||||
404: CommandNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: CommandNotFoundError | SessionNotFoundError
|
||||
/**
|
||||
* ConflictError
|
||||
*/
|
||||
@@ -15998,7 +15401,7 @@ export type V2SessionCommandErrors = {
|
||||
/**
|
||||
* CommandEvaluationError
|
||||
*/
|
||||
500: CommandEvaluationErrorV2
|
||||
500: CommandEvaluationError
|
||||
}
|
||||
|
||||
export type V2SessionCommandError = V2SessionCommandErrors[keyof V2SessionCommandErrors]
|
||||
@@ -16008,7 +15411,7 @@ export type V2SessionCommandResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: SessionInputAdmitted2
|
||||
data: SessionInputAdmittedV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16035,11 +15438,11 @@ export type V2SessionSkillErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | SkillNotFoundError
|
||||
*/
|
||||
404: SkillNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: SkillNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionSkillError = V2SessionSkillErrors[keyof V2SessionSkillErrors]
|
||||
@@ -16076,11 +15479,11 @@ export type V2SessionSyntheticErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionSyntheticError = V2SessionSyntheticErrors[keyof V2SessionSyntheticErrors]
|
||||
@@ -16114,11 +15517,11 @@ export type V2SessionShellErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionShellError = V2SessionShellErrors[keyof V2SessionShellErrors]
|
||||
@@ -16133,7 +15536,9 @@ export type V2SessionShellResponses = {
|
||||
export type V2SessionShellResponse = V2SessionShellResponses[keyof V2SessionShellResponses]
|
||||
|
||||
export type V2SessionCompactData = {
|
||||
body?: never
|
||||
body: {
|
||||
id?: string | null
|
||||
}
|
||||
path: {
|
||||
sessionID: string
|
||||
}
|
||||
@@ -16149,32 +15554,26 @@ export type V2SessionCompactErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* SessionBusyError
|
||||
* ConflictError
|
||||
*/
|
||||
409: SessionBusyErrorV2
|
||||
/**
|
||||
* UnknownError
|
||||
*/
|
||||
500: UnknownErrorV2
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
503: ServiceUnavailableErrorV2
|
||||
409: ConflictErrorV2
|
||||
}
|
||||
|
||||
export type V2SessionCompactError = V2SessionCompactErrors[keyof V2SessionCompactErrors]
|
||||
|
||||
export type V2SessionCompactResponses = {
|
||||
/**
|
||||
* <No Content>
|
||||
* Success
|
||||
*/
|
||||
204: void
|
||||
200: {
|
||||
data: SessionInputCompactionV2
|
||||
}
|
||||
}
|
||||
|
||||
export type V2SessionCompactResponse = V2SessionCompactResponses[keyof V2SessionCompactResponses]
|
||||
@@ -16196,11 +15595,11 @@ export type V2SessionWaitErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
@@ -16238,15 +15637,15 @@ export type V2SessionRevertStageErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* MessageNotFoundError | SessionNotFoundError
|
||||
*/
|
||||
404: MessageNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: MessageNotFoundError | SessionNotFoundError
|
||||
/**
|
||||
* SessionBusyError
|
||||
*/
|
||||
409: SessionBusyErrorV2
|
||||
409: SessionBusyError
|
||||
/**
|
||||
* UnknownError
|
||||
*/
|
||||
@@ -16260,7 +15659,7 @@ export type V2SessionRevertStageResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: RevertState2
|
||||
data: RevertStateV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16283,15 +15682,15 @@ export type V2SessionRevertClearErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* SessionBusyError
|
||||
*/
|
||||
409: SessionBusyErrorV2
|
||||
409: SessionBusyError
|
||||
/**
|
||||
* UnknownError
|
||||
*/
|
||||
@@ -16326,15 +15725,15 @@ export type V2SessionRevertCommitErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* SessionBusyError
|
||||
*/
|
||||
409: SessionBusyErrorV2
|
||||
409: SessionBusyError
|
||||
}
|
||||
|
||||
export type V2SessionRevertCommitError = V2SessionRevertCommitErrors[keyof V2SessionRevertCommitErrors]
|
||||
@@ -16365,11 +15764,11 @@ export type V2SessionContextErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* UnknownError
|
||||
*/
|
||||
@@ -16383,7 +15782,7 @@ export type V2SessionContextResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: Array<SessionMessage2>
|
||||
data: Array<SessionMessage>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16406,11 +15805,11 @@ export type V2SessionContextEntryListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionContextEntryListError = V2SessionContextEntryListErrors[keyof V2SessionContextEntryListErrors]
|
||||
@@ -16420,7 +15819,7 @@ export type V2SessionContextEntryListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: Array<SessionContextEntryInfo2>
|
||||
data: Array<SessionContextEntryInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16431,7 +15830,7 @@ export type V2SessionContextEntryRemoveData = {
|
||||
body?: never
|
||||
path: {
|
||||
sessionID: string
|
||||
key: SessionContextEntryKey2
|
||||
key: SessionContextEntryKeyV2
|
||||
}
|
||||
query?: never
|
||||
url: "/api/session/{sessionID}/context-entry/{key}"
|
||||
@@ -16445,11 +15844,11 @@ export type V2SessionContextEntryRemoveErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionContextEntryRemoveError =
|
||||
@@ -16471,7 +15870,7 @@ export type V2SessionContextEntryPutData = {
|
||||
}
|
||||
path: {
|
||||
sessionID: string
|
||||
key: SessionContextEntryKey2
|
||||
key: SessionContextEntryKeyV2
|
||||
}
|
||||
query?: never
|
||||
url: "/api/session/{sessionID}/context-entry/{key}"
|
||||
@@ -16485,11 +15884,11 @@ export type V2SessionContextEntryPutErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionContextEntryPutError = V2SessionContextEntryPutErrors[keyof V2SessionContextEntryPutErrors]
|
||||
@@ -16524,11 +15923,11 @@ export type V2SessionLogErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionLogError = V2SessionLogErrors[keyof V2SessionLogErrors]
|
||||
@@ -16540,7 +15939,7 @@ export type V2SessionLogResponses = {
|
||||
200: {
|
||||
id: string | null
|
||||
event: string
|
||||
data: SessionLogItemStreamV2
|
||||
data: SessionLogItemStream
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16563,11 +15962,11 @@ export type V2SessionInterruptErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionInterruptError = V2SessionInterruptErrors[keyof V2SessionInterruptErrors]
|
||||
@@ -16598,11 +15997,11 @@ export type V2SessionBackgroundErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionBackgroundError = V2SessionBackgroundErrors[keyof V2SessionBackgroundErrors]
|
||||
@@ -16634,11 +16033,11 @@ export type V2SessionMessageErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | MessageNotFoundError
|
||||
*/
|
||||
404: MessageNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: MessageNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionMessageError = V2SessionMessageErrors[keyof V2SessionMessageErrors]
|
||||
@@ -16648,7 +16047,7 @@ export type V2SessionMessageResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: SessionMessage2
|
||||
data: SessionMessage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16677,15 +16076,15 @@ export type V2SessionMessagesErrors = {
|
||||
/**
|
||||
* InvalidCursorError | InvalidRequestError
|
||||
*/
|
||||
400: InvalidCursorErrorV2 | InvalidRequestErrorV2
|
||||
400: InvalidCursorError | InvalidRequestErrorV2
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* UnknownError
|
||||
*/
|
||||
@@ -16723,7 +16122,7 @@ export type V2ModelListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
@@ -16737,8 +16136,8 @@ export type V2ModelListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<ModelV2Info2>
|
||||
location: LocationInfoV2
|
||||
data: Array<ModelV2Info>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16764,7 +16163,7 @@ export type V2ModelDefaultErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
@@ -16778,8 +16177,8 @@ export type V2ModelDefaultResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: ModelV2Info2 | null
|
||||
location: LocationInfoV2
|
||||
data: ModelV2Info | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16788,7 +16187,7 @@ export type V2ModelDefaultResponse = V2ModelDefaultResponses[keyof V2ModelDefaul
|
||||
export type V2GenerateTextData = {
|
||||
body: {
|
||||
prompt: string
|
||||
model?: ModelRef2 | null
|
||||
model?: ModelRef | null
|
||||
}
|
||||
path?: never
|
||||
query?: {
|
||||
@@ -16808,7 +16207,7 @@ export type V2GenerateTextErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
@@ -16821,7 +16220,7 @@ export type V2GenerateTextResponses = {
|
||||
/**
|
||||
* GenerateTextResponse
|
||||
*/
|
||||
200: GenerateTextResponseV2
|
||||
200: GenerateTextResponse
|
||||
}
|
||||
|
||||
export type V2GenerateTextResponse = V2GenerateTextResponses[keyof V2GenerateTextResponses]
|
||||
@@ -16846,7 +16245,7 @@ export type V2ProviderListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
@@ -16860,8 +16259,8 @@ export type V2ProviderListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<ProviderV2Info2>
|
||||
location: LocationInfoV2
|
||||
data: Array<ProviderV2Info>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16889,11 +16288,11 @@ export type V2ProviderGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ProviderNotFoundError
|
||||
*/
|
||||
404: ProviderNotFoundErrorV2
|
||||
404: ProviderNotFoundError
|
||||
/**
|
||||
* ServiceUnavailableError
|
||||
*/
|
||||
@@ -16907,8 +16306,8 @@ export type V2ProviderGetResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: ProviderV2Info2
|
||||
location: LocationInfoV2
|
||||
data: ProviderV2Info
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16934,7 +16333,7 @@ export type V2IntegrationListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2IntegrationListError = V2IntegrationListErrors[keyof V2IntegrationListErrors]
|
||||
@@ -16944,8 +16343,8 @@ export type V2IntegrationListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<IntegrationInfo2>
|
||||
location: LocationInfoV2
|
||||
data: Array<IntegrationInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16973,7 +16372,7 @@ export type V2IntegrationGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2IntegrationGetError = V2IntegrationGetErrors[keyof V2IntegrationGetErrors]
|
||||
@@ -16983,8 +16382,8 @@ export type V2IntegrationGetResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: IntegrationInfo2 | null
|
||||
location: LocationInfoV2
|
||||
data: IntegrationInfo | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17015,7 +16414,7 @@ export type V2IntegrationConnectKeyErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2IntegrationConnectKeyError = V2IntegrationConnectKeyErrors[keyof V2IntegrationConnectKeyErrors]
|
||||
@@ -17057,7 +16456,7 @@ export type V2IntegrationConnectOauthErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2IntegrationConnectOauthError = V2IntegrationConnectOauthErrors[keyof V2IntegrationConnectOauthErrors]
|
||||
@@ -17067,8 +16466,8 @@ export type V2IntegrationConnectOauthResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: IntegrationAttempt2
|
||||
location: LocationInfoV2
|
||||
data: IntegrationAttempt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17097,7 +16496,7 @@ export type V2IntegrationAttemptCancelErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2IntegrationAttemptCancelError = V2IntegrationAttemptCancelErrors[keyof V2IntegrationAttemptCancelErrors]
|
||||
@@ -17134,7 +16533,7 @@ export type V2IntegrationAttemptStatusErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2IntegrationAttemptStatusError = V2IntegrationAttemptStatusErrors[keyof V2IntegrationAttemptStatusErrors]
|
||||
@@ -17144,8 +16543,8 @@ export type V2IntegrationAttemptStatusResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: IntegrationAttemptStatus2
|
||||
location: LocationInfoV2
|
||||
data: IntegrationAttemptStatus
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17176,7 +16575,7 @@ export type V2IntegrationAttemptCompleteErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2IntegrationAttemptCompleteError =
|
||||
@@ -17212,7 +16611,7 @@ export type V2McpListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2McpListError = V2McpListErrors[keyof V2McpListErrors]
|
||||
@@ -17222,8 +16621,8 @@ export type V2McpListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<McpServer2>
|
||||
location: LocationInfoV2
|
||||
data: Array<McpServer>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17251,7 +16650,7 @@ export type V2CredentialRemoveErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2CredentialRemoveError = V2CredentialRemoveErrors[keyof V2CredentialRemoveErrors]
|
||||
@@ -17289,7 +16688,7 @@ export type V2CredentialUpdateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2CredentialUpdateError = V2CredentialUpdateErrors[keyof V2CredentialUpdateErrors]
|
||||
@@ -17323,7 +16722,7 @@ export type V2ProjectCurrentErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ProjectCurrentError = V2ProjectCurrentErrors[keyof V2ProjectCurrentErrors]
|
||||
@@ -17332,7 +16731,7 @@ export type V2ProjectCurrentResponses = {
|
||||
/**
|
||||
* Project.Current
|
||||
*/
|
||||
200: ProjectCurrent2
|
||||
200: ProjectCurrent
|
||||
}
|
||||
|
||||
export type V2ProjectCurrentResponse = V2ProjectCurrentResponses[keyof V2ProjectCurrentResponses]
|
||||
@@ -17359,7 +16758,7 @@ export type V2ProjectDirectoriesErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ProjectDirectoriesError = V2ProjectDirectoriesErrors[keyof V2ProjectDirectoriesErrors]
|
||||
@@ -17368,7 +16767,7 @@ export type V2ProjectDirectoriesResponses = {
|
||||
/**
|
||||
* Project.Directories
|
||||
*/
|
||||
200: ProjectDirectories2
|
||||
200: ProjectDirectories
|
||||
}
|
||||
|
||||
export type V2ProjectDirectoriesResponse = V2ProjectDirectoriesResponses[keyof V2ProjectDirectoriesResponses]
|
||||
@@ -17393,7 +16792,7 @@ export type V2FormRequestListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2FormRequestListError = V2FormRequestListErrors[keyof V2FormRequestListErrors]
|
||||
@@ -17403,8 +16802,8 @@ export type V2FormRequestListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<FormFormInfo2 | FormUrlInfo2>
|
||||
location: LocationInfoV2
|
||||
data: Array<FormFormInfoV2 | FormUrlInfoV2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17427,11 +16826,11 @@ export type V2SessionFormListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionFormListError = V2SessionFormListErrors[keyof V2SessionFormListErrors]
|
||||
@@ -17441,14 +16840,14 @@ export type V2SessionFormListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: Array<FormFormInfo2 | FormUrlInfo2>
|
||||
data: Array<FormFormInfoV2 | FormUrlInfoV2>
|
||||
}
|
||||
}
|
||||
|
||||
export type V2SessionFormListResponse = V2SessionFormListResponses[keyof V2SessionFormListResponses]
|
||||
|
||||
export type V2SessionFormCreateData = {
|
||||
body: FormCreatePayload2
|
||||
body: FormCreatePayloadV2
|
||||
path: {
|
||||
sessionID: string
|
||||
}
|
||||
@@ -17464,11 +16863,11 @@ export type V2SessionFormCreateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
/**
|
||||
* ConflictError
|
||||
*/
|
||||
@@ -17482,7 +16881,7 @@ export type V2SessionFormCreateResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: FormFormInfo2 | FormUrlInfo2
|
||||
data: FormFormInfoV2 | FormUrlInfoV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17506,11 +16905,11 @@ export type V2SessionFormGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | FormNotFoundError
|
||||
*/
|
||||
404: FormNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: FormNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionFormGetError = V2SessionFormGetErrors[keyof V2SessionFormGetErrors]
|
||||
@@ -17520,7 +16919,7 @@ export type V2SessionFormGetResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: FormFormInfo2 | FormUrlInfo2
|
||||
data: FormFormInfoV2 | FormUrlInfoV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17544,11 +16943,11 @@ export type V2SessionFormStateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | FormNotFoundError
|
||||
*/
|
||||
404: FormNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: FormNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionFormStateError = V2SessionFormStateErrors[keyof V2SessionFormStateErrors]
|
||||
@@ -17558,14 +16957,14 @@ export type V2SessionFormStateResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: FormState2
|
||||
data: FormState
|
||||
}
|
||||
}
|
||||
|
||||
export type V2SessionFormStateResponse = V2SessionFormStateResponses[keyof V2SessionFormStateResponses]
|
||||
|
||||
export type V2SessionFormReplyData = {
|
||||
body: FormReply2
|
||||
body: FormReply
|
||||
path: {
|
||||
sessionID: string
|
||||
formID: string
|
||||
@@ -17578,19 +16977,19 @@ export type V2SessionFormReplyErrors = {
|
||||
/**
|
||||
* FormInvalidAnswerError | InvalidRequestError
|
||||
*/
|
||||
400: FormInvalidAnswerErrorV2 | InvalidRequestErrorV2
|
||||
400: FormInvalidAnswerError | InvalidRequestErrorV2
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | FormNotFoundError
|
||||
*/
|
||||
404: FormNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: FormNotFoundError | SessionNotFoundError
|
||||
/**
|
||||
* FormAlreadySettledError
|
||||
*/
|
||||
409: FormAlreadySettledErrorV2
|
||||
409: FormAlreadySettledError
|
||||
}
|
||||
|
||||
export type V2SessionFormReplyError = V2SessionFormReplyErrors[keyof V2SessionFormReplyErrors]
|
||||
@@ -17622,15 +17021,15 @@ export type V2SessionFormCancelErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | FormNotFoundError
|
||||
*/
|
||||
404: FormNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: FormNotFoundError | SessionNotFoundError
|
||||
/**
|
||||
* FormAlreadySettledError
|
||||
*/
|
||||
409: FormAlreadySettledErrorV2
|
||||
409: FormAlreadySettledError
|
||||
}
|
||||
|
||||
export type V2SessionFormCancelError = V2SessionFormCancelErrors[keyof V2SessionFormCancelErrors]
|
||||
@@ -17664,7 +17063,7 @@ export type V2PermissionRequestListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2PermissionRequestListError = V2PermissionRequestListErrors[keyof V2PermissionRequestListErrors]
|
||||
@@ -17674,8 +17073,8 @@ export type V2PermissionRequestListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<PermissionV2Request2>
|
||||
location: LocationInfoV2
|
||||
data: Array<PermissionV2RequestV2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17698,7 +17097,7 @@ export type V2PermissionSavedListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2PermissionSavedListError = V2PermissionSavedListErrors[keyof V2PermissionSavedListErrors]
|
||||
@@ -17708,7 +17107,7 @@ export type V2PermissionSavedListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: Array<PermissionSavedInfo2>
|
||||
data: Array<PermissionSavedInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17731,7 +17130,7 @@ export type V2PermissionSavedRemoveErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2PermissionSavedRemoveError = V2PermissionSavedRemoveErrors[keyof V2PermissionSavedRemoveErrors]
|
||||
@@ -17762,11 +17161,11 @@ export type V2SessionPermissionListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionPermissionListError = V2SessionPermissionListErrors[keyof V2SessionPermissionListErrors]
|
||||
@@ -17776,7 +17175,7 @@ export type V2SessionPermissionListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: Array<PermissionV2Request2>
|
||||
data: Array<PermissionV2RequestV2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17791,7 +17190,7 @@ export type V2SessionPermissionCreateData = {
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2Source2
|
||||
source?: PermissionV2SourceV2
|
||||
agent?: string | null
|
||||
}
|
||||
path: {
|
||||
@@ -17809,11 +17208,11 @@ export type V2SessionPermissionCreateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionPermissionCreateError = V2SessionPermissionCreateErrors[keyof V2SessionPermissionCreateErrors]
|
||||
@@ -17825,7 +17224,7 @@ export type V2SessionPermissionCreateResponses = {
|
||||
200: {
|
||||
data: {
|
||||
id: string
|
||||
effect: PermissionV2Effect2
|
||||
effect: PermissionV2Effect
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17851,11 +17250,11 @@ export type V2SessionPermissionGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | PermissionNotFoundError
|
||||
*/
|
||||
404: PermissionNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: PermissionNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionPermissionGetError = V2SessionPermissionGetErrors[keyof V2SessionPermissionGetErrors]
|
||||
@@ -17865,7 +17264,7 @@ export type V2SessionPermissionGetResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: PermissionV2Request2
|
||||
data: PermissionV2RequestV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17873,7 +17272,7 @@ export type V2SessionPermissionGetResponse = V2SessionPermissionGetResponses[key
|
||||
|
||||
export type V2SessionPermissionReplyData = {
|
||||
body: {
|
||||
reply: PermissionV2Reply2
|
||||
reply: PermissionV2Reply
|
||||
message?: string | null
|
||||
}
|
||||
path: {
|
||||
@@ -17892,11 +17291,11 @@ export type V2SessionPermissionReplyErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | PermissionNotFoundError
|
||||
*/
|
||||
404: PermissionNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: PermissionNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionPermissionReplyError = V2SessionPermissionReplyErrors[keyof V2SessionPermissionReplyErrors]
|
||||
@@ -17931,7 +17330,7 @@ export type V2FsReadErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2FsReadError = V2FsReadErrors[keyof V2FsReadErrors]
|
||||
@@ -17966,7 +17365,7 @@ export type V2FsListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2FsListError = V2FsListErrors[keyof V2FsListErrors]
|
||||
@@ -17976,8 +17375,8 @@ export type V2FsListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<FileSystemEntry2>
|
||||
location: LocationInfoV2
|
||||
data: Array<FileSystemEntry>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18006,7 +17405,7 @@ export type V2FsFindErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2FsFindError = V2FsFindErrors[keyof V2FsFindErrors]
|
||||
@@ -18016,8 +17415,8 @@ export type V2FsFindResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<FileSystemEntry2>
|
||||
location: LocationInfoV2
|
||||
data: Array<FileSystemEntry>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18043,7 +17442,7 @@ export type V2CommandListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2CommandListError = V2CommandListErrors[keyof V2CommandListErrors]
|
||||
@@ -18053,8 +17452,8 @@ export type V2CommandListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<CommandV2Info2>
|
||||
location: LocationInfoV2
|
||||
data: Array<CommandV2Info>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18080,7 +17479,7 @@ export type V2SkillListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2SkillListError = V2SkillListErrors[keyof V2SkillListErrors]
|
||||
@@ -18090,8 +17489,8 @@ export type V2SkillListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<SkillV2Info2>
|
||||
location: LocationInfoV2
|
||||
data: Array<SkillV2Info>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18112,7 +17511,7 @@ export type V2EventSubscribeErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2EventSubscribeError = V2EventSubscribeErrors[keyof V2EventSubscribeErrors]
|
||||
@@ -18141,7 +17540,7 @@ export type V2EventChangesErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2EventChangesError = V2EventChangesErrors[keyof V2EventChangesErrors]
|
||||
@@ -18153,7 +17552,7 @@ export type V2EventChangesResponses = {
|
||||
200: {
|
||||
id: string | null
|
||||
event: string
|
||||
data: EventLogChangeStream2
|
||||
data: EventLogChangeStream
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18179,7 +17578,7 @@ export type V2PtyListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2PtyListError = V2PtyListErrors[keyof V2PtyListErrors]
|
||||
@@ -18189,7 +17588,7 @@ export type V2PtyListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: Array<PtyV2>
|
||||
}
|
||||
}
|
||||
@@ -18224,7 +17623,7 @@ export type V2PtyCreateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2PtyCreateError = V2PtyCreateErrors[keyof V2PtyCreateErrors]
|
||||
@@ -18234,7 +17633,7 @@ export type V2PtyCreateResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: PtyV2
|
||||
}
|
||||
}
|
||||
@@ -18263,11 +17662,11 @@ export type V2PtyRemoveErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* PtyNotFoundError
|
||||
*/
|
||||
404: PtyNotFoundErrorV2
|
||||
404: PtyNotFoundError
|
||||
}
|
||||
|
||||
export type V2PtyRemoveError = V2PtyRemoveErrors[keyof V2PtyRemoveErrors]
|
||||
@@ -18303,11 +17702,11 @@ export type V2PtyGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* PtyNotFoundError
|
||||
*/
|
||||
404: PtyNotFoundErrorV2
|
||||
404: PtyNotFoundError
|
||||
}
|
||||
|
||||
export type V2PtyGetError = V2PtyGetErrors[keyof V2PtyGetErrors]
|
||||
@@ -18317,7 +17716,7 @@ export type V2PtyGetResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: PtyV2
|
||||
}
|
||||
}
|
||||
@@ -18352,11 +17751,11 @@ export type V2PtyUpdateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* PtyNotFoundError
|
||||
*/
|
||||
404: PtyNotFoundErrorV2
|
||||
404: PtyNotFoundError
|
||||
}
|
||||
|
||||
export type V2PtyUpdateError = V2PtyUpdateErrors[keyof V2PtyUpdateErrors]
|
||||
@@ -18366,7 +17765,7 @@ export type V2PtyUpdateResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: PtyV2
|
||||
}
|
||||
}
|
||||
@@ -18395,15 +17794,15 @@ export type V2PtyConnectTokenErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ForbiddenError
|
||||
*/
|
||||
403: ForbiddenErrorV2
|
||||
403: ForbiddenError
|
||||
/**
|
||||
* PtyNotFoundError
|
||||
*/
|
||||
404: PtyNotFoundErrorV2
|
||||
404: PtyNotFoundError
|
||||
}
|
||||
|
||||
export type V2PtyConnectTokenError = V2PtyConnectTokenErrors[keyof V2PtyConnectTokenErrors]
|
||||
@@ -18413,8 +17812,8 @@ export type V2PtyConnectTokenResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: PtyTicketConnectToken2
|
||||
location: LocationInfoV2
|
||||
data: PtyTicketConnectTokenV2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18442,15 +17841,15 @@ export type V2PtyConnectErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ForbiddenError
|
||||
*/
|
||||
403: ForbiddenErrorV2
|
||||
403: ForbiddenError
|
||||
/**
|
||||
* PtyNotFoundError
|
||||
*/
|
||||
404: PtyNotFoundErrorV2
|
||||
404: PtyNotFoundError
|
||||
}
|
||||
|
||||
export type V2PtyConnectError = V2PtyConnectErrors[keyof V2PtyConnectErrors]
|
||||
@@ -18484,7 +17883,7 @@ export type V2ShellListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ShellListError = V2ShellListErrors[keyof V2ShellListErrors]
|
||||
@@ -18494,7 +17893,7 @@ export type V2ShellListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: Array<ShellV2>
|
||||
}
|
||||
}
|
||||
@@ -18528,7 +17927,7 @@ export type V2ShellCreateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ShellCreateError = V2ShellCreateErrors[keyof V2ShellCreateErrors]
|
||||
@@ -18538,7 +17937,7 @@ export type V2ShellCreateResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: ShellV2
|
||||
}
|
||||
}
|
||||
@@ -18567,11 +17966,11 @@ export type V2ShellRemoveErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ShellNotFoundError
|
||||
*/
|
||||
404: ShellNotFoundErrorV2
|
||||
404: ShellNotFoundError
|
||||
}
|
||||
|
||||
export type V2ShellRemoveError = V2ShellRemoveErrors[keyof V2ShellRemoveErrors]
|
||||
@@ -18607,11 +18006,11 @@ export type V2ShellGetErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ShellNotFoundError
|
||||
*/
|
||||
404: ShellNotFoundErrorV2
|
||||
404: ShellNotFoundError
|
||||
}
|
||||
|
||||
export type V2ShellGetError = V2ShellGetErrors[keyof V2ShellGetErrors]
|
||||
@@ -18621,7 +18020,7 @@ export type V2ShellGetResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: ShellV2
|
||||
}
|
||||
}
|
||||
@@ -18652,11 +18051,11 @@ export type V2ShellOutputErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* ShellNotFoundError
|
||||
*/
|
||||
404: ShellNotFoundErrorV2
|
||||
404: ShellNotFoundError
|
||||
}
|
||||
|
||||
export type V2ShellOutputError = V2ShellOutputErrors[keyof V2ShellOutputErrors]
|
||||
@@ -18666,7 +18065,7 @@ export type V2ShellOutputResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: {
|
||||
output: string
|
||||
cursor: number
|
||||
@@ -18698,7 +18097,7 @@ export type V2QuestionRequestListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2QuestionRequestListError = V2QuestionRequestListErrors[keyof V2QuestionRequestListErrors]
|
||||
@@ -18708,8 +18107,8 @@ export type V2QuestionRequestListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<QuestionV2Request2>
|
||||
location: LocationInfoV2
|
||||
data: Array<QuestionV2RequestV2>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18732,11 +18131,11 @@ export type V2SessionQuestionListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundErrorV2
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionQuestionListError = V2SessionQuestionListErrors[keyof V2SessionQuestionListErrors]
|
||||
@@ -18746,14 +18145,14 @@ export type V2SessionQuestionListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: Array<QuestionV2Request2>
|
||||
data: Array<QuestionV2RequestV2>
|
||||
}
|
||||
}
|
||||
|
||||
export type V2SessionQuestionListResponse = V2SessionQuestionListResponses[keyof V2SessionQuestionListResponses]
|
||||
|
||||
export type V2SessionQuestionReplyData = {
|
||||
body: QuestionV2Reply2
|
||||
body: QuestionV2Reply
|
||||
path: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
@@ -18770,11 +18169,11 @@ export type V2SessionQuestionReplyErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | QuestionNotFoundError
|
||||
*/
|
||||
404: QuestionNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: QuestionNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionQuestionReplyError = V2SessionQuestionReplyErrors[keyof V2SessionQuestionReplyErrors]
|
||||
@@ -18806,11 +18205,11 @@ export type V2SessionQuestionRejectErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | QuestionNotFoundError
|
||||
*/
|
||||
404: QuestionNotFoundErrorV2 | SessionNotFoundErrorV2
|
||||
404: QuestionNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionQuestionRejectError = V2SessionQuestionRejectErrors[keyof V2SessionQuestionRejectErrors]
|
||||
@@ -18844,7 +18243,7 @@ export type V2ReferenceListErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ReferenceListError = V2ReferenceListErrors[keyof V2ReferenceListErrors]
|
||||
@@ -18854,8 +18253,8 @@ export type V2ReferenceListResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<ReferenceInfo2>
|
||||
location: LocationInfoV2
|
||||
data: Array<ReferenceInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18886,7 +18285,7 @@ export type V2ProjectCopyRemoveErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ProjectCopyRemoveError = V2ProjectCopyRemoveErrors[keyof V2ProjectCopyRemoveErrors]
|
||||
@@ -18926,7 +18325,7 @@ export type V2ProjectCopyCreateErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ProjectCopyCreateError = V2ProjectCopyCreateErrors[keyof V2ProjectCopyCreateErrors]
|
||||
@@ -18935,7 +18334,7 @@ export type V2ProjectCopyCreateResponses = {
|
||||
/**
|
||||
* ProjectCopy.Copy
|
||||
*/
|
||||
200: ProjectCopyCopy2
|
||||
200: ProjectCopyCopy
|
||||
}
|
||||
|
||||
export type V2ProjectCopyCreateResponse = V2ProjectCopyCreateResponses[keyof V2ProjectCopyCreateResponses]
|
||||
@@ -18962,7 +18361,7 @@ export type V2ProjectCopyRefreshErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2ProjectCopyRefreshError = V2ProjectCopyRefreshErrors[keyof V2ProjectCopyRefreshErrors]
|
||||
@@ -18996,7 +18395,7 @@ export type V2VcsStatusErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2VcsStatusError = V2VcsStatusErrors[keyof V2VcsStatusErrors]
|
||||
@@ -19006,7 +18405,7 @@ export type V2VcsStatusResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
location: LocationInfoV2
|
||||
data: Array<VcsFileStatusV2>
|
||||
}
|
||||
}
|
||||
@@ -19021,7 +18420,7 @@ export type V2VcsDiffData = {
|
||||
directory?: string | null
|
||||
workspace?: string | null
|
||||
} | null
|
||||
mode: VcsMode2
|
||||
mode: VcsMode
|
||||
context?: string | null
|
||||
}
|
||||
url: "/api/vcs/diff"
|
||||
@@ -19035,7 +18434,7 @@ export type V2VcsDiffErrors = {
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedErrorV2
|
||||
401: UnauthorizedError
|
||||
}
|
||||
|
||||
export type V2VcsDiffError = V2VcsDiffErrors[keyof V2VcsDiffErrors]
|
||||
@@ -19045,8 +18444,8 @@ export type V2VcsDiffResponses = {
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
location: LocationInfo2
|
||||
data: Array<SnapshotFileDiffV2>
|
||||
location: LocationInfoV2
|
||||
data: Array<SnapshotFileDiff>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -344,44 +344,26 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||
.handle(
|
||||
"session.compact",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* session.compact({ sessionID: ctx.params.sessionID }).pipe(
|
||||
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||
Effect.fail(
|
||||
new SessionNotFoundError({
|
||||
sessionID: error.sessionID,
|
||||
message: `Session not found: ${error.sessionID}`,
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.catchTag("Session.OperationUnavailableError", (error) =>
|
||||
Effect.fail(
|
||||
new ServiceUnavailableError({
|
||||
message: `Session ${error.operation} is not available yet`,
|
||||
service: `session.${error.operation}`,
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.catchTag(
|
||||
"Session.BusyError",
|
||||
(error) =>
|
||||
new SessionBusyError({
|
||||
sessionID: error.sessionID,
|
||||
message: `Session is busy: ${error.sessionID}`,
|
||||
}),
|
||||
),
|
||||
Effect.catchTag("Session.MessageDecodeError", (error) => {
|
||||
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
||||
return Effect.logError("failed to decode session message during compaction").pipe(
|
||||
Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }),
|
||||
Effect.andThen(
|
||||
Effect.fail(
|
||||
new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }),
|
||||
),
|
||||
return {
|
||||
data: yield* session.compact({ sessionID: ctx.params.sessionID, id: ctx.payload.id }).pipe(
|
||||
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||
Effect.fail(
|
||||
new SessionNotFoundError({
|
||||
sessionID: error.sessionID,
|
||||
message: `Session not found: ${error.sessionID}`,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
),
|
||||
Effect.catchTag("Session.CompactionConflictError", (error) =>
|
||||
Effect.fail(
|
||||
new ConflictError({
|
||||
message: `Compaction input ID conflicts with an existing durable record: ${error.inputID}`,
|
||||
resource: error.inputID,
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
|
||||
@@ -20,7 +20,7 @@ import type {
|
||||
SkillV2Info,
|
||||
V2Event,
|
||||
} from "@opencode-ai/sdk/v2"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { useSDK } from "./sdk"
|
||||
import { createSignal, onCleanup } from "solid-js"
|
||||
@@ -51,6 +51,8 @@ type Data = {
|
||||
// session ID in that family, including the key itself once its info arrives.
|
||||
family: Record<string, string[]>
|
||||
status: Record<string, DataSessionStatus>
|
||||
compaction: Partial<Record<string, string>>
|
||||
compactionReason: Partial<Record<string, "auto" | "manual">>
|
||||
message: Record<string, SessionMessage[]>
|
||||
permission: Record<string, PermissionV2Request[]>
|
||||
question: Record<string, QuestionV2Request[]>
|
||||
@@ -85,6 +87,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
info: {},
|
||||
family: {},
|
||||
status: {},
|
||||
compaction: {},
|
||||
compactionReason: {},
|
||||
message: {},
|
||||
permission: {},
|
||||
question: {},
|
||||
@@ -100,8 +104,15 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
directory: process.cwd(),
|
||||
})
|
||||
const messageIndex = new Map<string, Map<string, number>>()
|
||||
let connectionGeneration = 0
|
||||
let statusChanges: Set<string> | undefined
|
||||
let bootstrapping: Promise<void> | undefined
|
||||
|
||||
function setSessionStatus(sessionID: string, status: DataSessionStatus) {
|
||||
statusChanges?.add(sessionID)
|
||||
setStore("session", "status", sessionID, status)
|
||||
}
|
||||
|
||||
const message = {
|
||||
update(sessionID: string, fn: (messages: SessionMessage[], index: Map<string, number>) => void) {
|
||||
setStore(
|
||||
@@ -130,20 +141,24 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
const item = messages.findLast((item) => item.type === "shell" && item.shell.id === shellID)
|
||||
return item?.type === "shell" ? item : undefined
|
||||
},
|
||||
compaction(messages: SessionMessage[]) {
|
||||
const item = messages.findLast(
|
||||
(item) => item.type === "compaction" && (item.status === "queued" || item.status === "running"),
|
||||
)
|
||||
return item?.type === "compaction" ? item : undefined
|
||||
},
|
||||
latestTool(assistant: SessionMessageAssistant | undefined, callID?: string) {
|
||||
return assistant?.content.findLast(
|
||||
(item): item is SessionMessageAssistantTool =>
|
||||
item.type === "tool" && (callID === undefined || item.id === callID),
|
||||
)
|
||||
},
|
||||
latestText(assistant: SessionMessageAssistant | undefined, textID: string) {
|
||||
return assistant?.content.findLast(
|
||||
(item): item is SessionMessageAssistantText => item.type === "text" && item.id === textID,
|
||||
)
|
||||
latestText(assistant: SessionMessageAssistant | undefined) {
|
||||
return assistant?.content.findLast((item): item is SessionMessageAssistantText => item.type === "text")
|
||||
},
|
||||
latestReasoning(assistant: SessionMessageAssistant | undefined, reasoningID: string) {
|
||||
latestReasoning(assistant: SessionMessageAssistant | undefined) {
|
||||
return assistant?.content.findLast(
|
||||
(item): item is SessionMessageAssistantReasoning => item.type === "reasoning" && item.id === reasoningID,
|
||||
(item): item is SessionMessageAssistantReasoning => item.type === "reasoning" && !item.time?.completed,
|
||||
)
|
||||
},
|
||||
}
|
||||
@@ -235,6 +250,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
case "session.model.selected":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
setStore("session", "info", event.data.sessionID, "model", event.data.model)
|
||||
if (!store.session.message[event.data.sessionID]) break
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.append(draft, index, {
|
||||
id: messageIDFromEvent(event.id),
|
||||
@@ -243,22 +259,34 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
time: { created: event.created },
|
||||
})
|
||||
})
|
||||
void sdk.api.session
|
||||
.message({ sessionID: event.data.sessionID, messageID: messageIDFromEvent(event.id) })
|
||||
.then((item) => {
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const position = index.get(item.id)
|
||||
if (position === undefined) return message.append(draft, index, mutable(item))
|
||||
draft[position] = mutable(item)
|
||||
})
|
||||
})
|
||||
.catch((error) => console.error("Failed to load projected model switch message", error))
|
||||
break
|
||||
case "session.renamed":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
setStore("session", "info", event.data.sessionID, "title", event.data.title)
|
||||
break
|
||||
case "session.prompt.promoted": {
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const position = index.get(event.data.inputID)
|
||||
const existing = position === undefined ? undefined : draft[position]
|
||||
if (existing?.type === "user") {
|
||||
if (position === undefined) return
|
||||
const existing = draft[position]
|
||||
if (existing?.type === "user" && existing.metadata?.queued === true) {
|
||||
existing.time.created = event.created
|
||||
if (existing.metadata?.queued === true) {
|
||||
delete existing.metadata.queued
|
||||
if (Object.keys(existing.metadata).length === 0) existing.metadata = undefined
|
||||
}
|
||||
delete existing.metadata.queued
|
||||
if (Object.keys(existing.metadata).length === 0) existing.metadata = undefined
|
||||
draft.splice(position, 1)
|
||||
draft.push(existing)
|
||||
index.clear()
|
||||
draft.forEach((message, indexValue) => index.set(message.id, indexValue))
|
||||
return
|
||||
}
|
||||
})
|
||||
@@ -300,7 +328,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
})
|
||||
break
|
||||
case "session.shell.started":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.append(draft, index, {
|
||||
id: messageIDFromEvent(event.id),
|
||||
@@ -311,7 +338,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
})
|
||||
break
|
||||
case "session.shell.ended":
|
||||
setStore("session", "status", event.data.sessionID, "idle")
|
||||
message.update(event.data.sessionID, (draft) => {
|
||||
const match = message.shell(draft, event.data.shell.id)
|
||||
if (!match) return
|
||||
@@ -321,11 +347,24 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
})
|
||||
break
|
||||
case "session.step.started":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
if (index.has(event.data.assistantMessageID)) return
|
||||
const position = index.get(event.data.assistantMessageID)
|
||||
const existing = position === undefined ? undefined : draft[position]
|
||||
if (existing?.type === "assistant") {
|
||||
existing.agent = event.data.agent
|
||||
existing.model = event.data.model
|
||||
existing.retry = undefined
|
||||
existing.error = undefined
|
||||
existing.finish = undefined
|
||||
existing.time.completed = undefined
|
||||
if (event.data.snapshot) existing.snapshot = { ...existing.snapshot, start: event.data.snapshot }
|
||||
return
|
||||
}
|
||||
const currentAssistant = message.activeAssistant(draft)
|
||||
if (currentAssistant) currentAssistant.time.completed = event.created
|
||||
if (currentAssistant) {
|
||||
currentAssistant.retry = undefined
|
||||
currentAssistant.time.completed = event.created
|
||||
}
|
||||
message.append(draft, index, {
|
||||
id: event.data.assistantMessageID,
|
||||
type: "assistant",
|
||||
@@ -338,7 +377,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
})
|
||||
break
|
||||
case "session.step.ended":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID)
|
||||
if (!currentAssistant) return
|
||||
@@ -357,32 +395,26 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
currentAssistant.time.completed = event.created
|
||||
currentAssistant.finish = "error"
|
||||
currentAssistant.error = event.data.error
|
||||
currentAssistant.retry = undefined
|
||||
})
|
||||
break
|
||||
case "session.text.started":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
|
||||
type: "text",
|
||||
id: event.data.textID,
|
||||
text: "",
|
||||
})
|
||||
})
|
||||
break
|
||||
case "session.text.delta":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestText(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
event.data.textID,
|
||||
)
|
||||
const match = message.latestText(message.assistant(draft, index, event.data.assistantMessageID))
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
break
|
||||
case "session.text.ended":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestText(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
event.data.textID,
|
||||
)
|
||||
const match = message.latestText(message.assistant(draft, index, event.data.assistantMessageID))
|
||||
if (match) match.text = event.data.text
|
||||
})
|
||||
break
|
||||
@@ -423,7 +455,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
)
|
||||
if (!match) return
|
||||
match.time.ran = event.created
|
||||
match.provider = event.data.provider
|
||||
match.executed = event.data.executed
|
||||
match.providerState = event.data.state
|
||||
match.state = { status: "running", input: event.data.input, structured: {}, content: [] }
|
||||
})
|
||||
break
|
||||
@@ -452,11 +485,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
content: [...event.data.content],
|
||||
result: event.data.result,
|
||||
}
|
||||
match.provider = {
|
||||
executed: event.data.provider.executed || match.provider?.executed === true,
|
||||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.data.provider.metadata,
|
||||
}
|
||||
match.executed = event.data.executed || match.executed === true
|
||||
match.providerResultState = event.data.resultState
|
||||
match.time.completed = event.created
|
||||
})
|
||||
break
|
||||
@@ -475,11 +505,8 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
content: match.state.status === "running" ? match.state.content : [],
|
||||
result: event.data.result,
|
||||
}
|
||||
match.provider = {
|
||||
executed: event.data.provider.executed || match.provider?.executed === true,
|
||||
metadata: match.provider?.metadata,
|
||||
resultMetadata: event.data.provider.metadata,
|
||||
}
|
||||
match.executed = event.data.executed || match.executed === true
|
||||
match.providerResultState = event.data.resultState
|
||||
match.time.completed = event.created
|
||||
})
|
||||
break
|
||||
@@ -487,41 +514,77 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
message.assistant(draft, index, event.data.assistantMessageID)?.content.push({
|
||||
type: "reasoning",
|
||||
id: event.data.reasoningID,
|
||||
text: "",
|
||||
providerMetadata: event.data.providerMetadata,
|
||||
state: event.data.state,
|
||||
time: { created: event.created },
|
||||
})
|
||||
})
|
||||
break
|
||||
case "session.reasoning.delta":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestReasoning(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
event.data.reasoningID,
|
||||
)
|
||||
const match = message.latestReasoning(message.assistant(draft, index, event.data.assistantMessageID))
|
||||
if (match) match.text += event.data.delta
|
||||
})
|
||||
break
|
||||
case "session.reasoning.ended":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const match = message.latestReasoning(
|
||||
message.assistant(draft, index, event.data.assistantMessageID),
|
||||
event.data.reasoningID,
|
||||
)
|
||||
const match = message.latestReasoning(message.assistant(draft, index, event.data.assistantMessageID))
|
||||
if (match) {
|
||||
match.text = event.data.text
|
||||
match.time = { created: match.time?.created ?? event.created, completed: event.created }
|
||||
if (event.data.providerMetadata !== undefined) match.providerMetadata = event.data.providerMetadata
|
||||
if (event.data.state !== undefined) match.state = event.data.state
|
||||
}
|
||||
})
|
||||
break
|
||||
case "session.retried":
|
||||
case "session.compaction.started":
|
||||
setStore("session", "status", event.data.sessionID, "running")
|
||||
case "session.retry.scheduled":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const currentAssistant = message.assistant(draft, index, event.data.assistantMessageID)
|
||||
if (!currentAssistant) return
|
||||
currentAssistant.retry = {
|
||||
attempt: event.data.attempt,
|
||||
at: event.data.at,
|
||||
error: event.data.error,
|
||||
}
|
||||
})
|
||||
break
|
||||
case "session.execution.settled":
|
||||
setStore("session", "status", event.data.sessionID, "idle")
|
||||
case "session.execution.started":
|
||||
setSessionStatus(event.data.sessionID, "running")
|
||||
break
|
||||
case "session.compaction.admitted":
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
if (message.compaction(draft)) return
|
||||
message.append(draft, index, {
|
||||
id: event.data.inputID,
|
||||
type: "compaction",
|
||||
status: "queued",
|
||||
reason: "manual",
|
||||
summary: "",
|
||||
recent: "",
|
||||
time: { created: event.created },
|
||||
})
|
||||
})
|
||||
break
|
||||
case "session.compaction.started":
|
||||
setStore("session", "compaction", event.data.sessionID, "")
|
||||
setStore("session", "compactionReason", event.data.sessionID, event.data.reason)
|
||||
if (event.data.reason === "manual")
|
||||
message.update(event.data.sessionID, (draft) => {
|
||||
const current = message.compaction(draft)
|
||||
if (current) current.status = "running"
|
||||
})
|
||||
break
|
||||
case "session.execution.succeeded":
|
||||
case "session.execution.failed":
|
||||
case "session.execution.interrupted":
|
||||
setSessionStatus(event.data.sessionID, "idle")
|
||||
if (store.session.compaction[event.data.sessionID] !== undefined)
|
||||
setStore("session", "compaction", event.data.sessionID, undefined)
|
||||
if (store.session.compactionReason[event.data.sessionID] !== undefined)
|
||||
setStore("session", "compactionReason", event.data.sessionID, undefined)
|
||||
message.update(event.data.sessionID, (draft) => {
|
||||
const currentAssistant = message.activeAssistant(draft)
|
||||
if (currentAssistant) currentAssistant.retry = undefined
|
||||
})
|
||||
break
|
||||
case "session.revert.staged":
|
||||
if (store.session.info[event.data.sessionID])
|
||||
@@ -533,12 +596,29 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
setStore("session", "info", event.data.sessionID, "revert", undefined)
|
||||
break
|
||||
case "session.compaction.delta":
|
||||
setStore("session", "compaction", event.data.sessionID, (text) => (text ?? "") + event.data.text)
|
||||
if (store.session.compactionReason[event.data.sessionID] === "manual")
|
||||
message.update(event.data.sessionID, (draft) => {
|
||||
const current = message.compaction(draft)
|
||||
if (current) current.summary += event.data.text
|
||||
})
|
||||
break
|
||||
case "session.compaction.ended":
|
||||
setStore("session", "compaction", event.data.sessionID, undefined)
|
||||
setStore("session", "compactionReason", event.data.sessionID, undefined)
|
||||
message.update(event.data.sessionID, (draft, index) => {
|
||||
const current = event.data.reason === "manual" ? message.compaction(draft) : undefined
|
||||
if (current) {
|
||||
current.status = "completed"
|
||||
current.reason = event.data.reason
|
||||
current.summary = event.data.text
|
||||
current.recent = event.data.recent
|
||||
return
|
||||
}
|
||||
message.append(draft, index, {
|
||||
id: messageIDFromEvent(event.id),
|
||||
type: "compaction",
|
||||
status: "completed",
|
||||
reason: event.data.reason,
|
||||
summary: event.data.text,
|
||||
recent: event.data.recent,
|
||||
@@ -546,6 +626,14 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
})
|
||||
})
|
||||
break
|
||||
case "session.compaction.failed":
|
||||
setStore("session", "compaction", event.data.sessionID, undefined)
|
||||
setStore("session", "compactionReason", event.data.sessionID, undefined)
|
||||
message.update(event.data.sessionID, (draft) => {
|
||||
const current = message.compaction(draft)
|
||||
if (current) current.status = "failed"
|
||||
})
|
||||
break
|
||||
case "permission.v2.asked":
|
||||
if (store.session.permission[event.data.sessionID]?.some((request) => request.id === event.data.id)) break
|
||||
setStore("session", "permission", event.data.sessionID, [
|
||||
@@ -655,6 +743,9 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
status(sessionID: string) {
|
||||
return store.session.status[sessionID] ?? "idle"
|
||||
},
|
||||
compaction(sessionID: string) {
|
||||
return store.session.compaction[sessionID]
|
||||
},
|
||||
async refresh(sessionID: string) {
|
||||
setStore("session", "info", sessionID, mutable(await sdk.api.session.get({ sessionID })))
|
||||
registerSession(sessionID)
|
||||
@@ -858,15 +949,6 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
)
|
||||
for (const session of response.data) registerSession(session.id)
|
||||
}),
|
||||
sdk.api.session
|
||||
.active()
|
||||
.then((active) =>
|
||||
setStore(
|
||||
"session",
|
||||
"status",
|
||||
Object.fromEntries(Object.keys(active.data).map((sessionID) => [sessionID, "running" as const])),
|
||||
),
|
||||
),
|
||||
result.location.refresh(),
|
||||
result.location.agent.refresh(),
|
||||
result.location.integration.refresh(),
|
||||
@@ -888,9 +970,30 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
return bootstrapping
|
||||
}
|
||||
|
||||
function refreshActive() {
|
||||
const generation = ++connectionGeneration
|
||||
const changed = new Set<string>()
|
||||
statusChanges = changed
|
||||
void sdk.api.session
|
||||
.active()
|
||||
.then((active) => {
|
||||
if (generation !== connectionGeneration) return
|
||||
const status: Record<string, DataSessionStatus> = Object.fromEntries(
|
||||
Object.keys(active.data).map((sessionID) => [sessionID, "running" as const]),
|
||||
)
|
||||
for (const sessionID of changed) status[sessionID] = store.session.status[sessionID]
|
||||
setStore("session", "status", reconcile(status))
|
||||
})
|
||||
.catch(() => undefined)
|
||||
.finally(() => {
|
||||
if (statusChanges === changed) statusChanges = undefined
|
||||
})
|
||||
}
|
||||
|
||||
onCleanup(
|
||||
sdk.event.listen(({ details }) => {
|
||||
if (details.type === "server.connected") {
|
||||
refreshActive()
|
||||
void bootstrap()
|
||||
return
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user