feat(core): resolve prompt attachments
This commit is contained in:
parent
c13f06c30c
commit
4d100de194
29 changed files with 610 additions and 426 deletions
|
|
@ -477,7 +477,7 @@ export function make(options: ClientOptions) {
|
||||||
path: `/api/session/${encodeURIComponent(input.sessionID)}/prompt`,
|
path: `/api/session/${encodeURIComponent(input.sessionID)}/prompt`,
|
||||||
body: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
|
body: { id: input["id"], prompt: input["prompt"], delivery: input["delivery"], resume: input["resume"] },
|
||||||
successStatus: 200,
|
successStatus: 200,
|
||||||
declaredStatuses: [409, 404, 400, 401],
|
declaredStatuses: [409, 400, 404, 401],
|
||||||
empty: false,
|
empty: false,
|
||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
|
|
@ -499,7 +499,7 @@ export function make(options: ClientOptions) {
|
||||||
resume: input["resume"],
|
resume: input["resume"],
|
||||||
},
|
},
|
||||||
successStatus: 200,
|
successStatus: 200,
|
||||||
declaredStatuses: [409, 404, 500, 400, 401],
|
declaredStatuses: [409, 400, 404, 500, 401],
|
||||||
empty: false,
|
empty: false,
|
||||||
},
|
},
|
||||||
requestOptions,
|
requestOptions,
|
||||||
|
|
|
||||||
|
|
@ -528,11 +528,11 @@ export type SessionPromptInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
|
|
@ -546,11 +546,11 @@ export type SessionPromptInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
|
|
@ -564,11 +564,11 @@ export type SessionPromptInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
|
|
@ -582,11 +582,11 @@ export type SessionPromptInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
|
|
@ -602,16 +602,16 @@ export type SessionPromptOutput = {
|
||||||
readonly prompt: {
|
readonly prompt: {
|
||||||
readonly text: string
|
readonly text: string
|
||||||
readonly files?: ReadonlyArray<{
|
readonly files?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery: "steer" | "queue"
|
readonly delivery: "steer" | "queue"
|
||||||
|
|
@ -632,11 +632,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -651,11 +651,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -670,11 +670,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -689,11 +689,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -708,11 +708,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -727,11 +727,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -746,11 +746,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -765,11 +765,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -784,11 +784,11 @@ export type SessionCommandInput = {
|
||||||
readonly uri: string
|
readonly uri: string
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly delivery?: "steer" | "queue" | null
|
readonly delivery?: "steer" | "queue" | null
|
||||||
readonly resume?: boolean | null
|
readonly resume?: boolean | null
|
||||||
|
|
@ -803,16 +803,16 @@ export type SessionCommandOutput = {
|
||||||
readonly prompt: {
|
readonly prompt: {
|
||||||
readonly text: string
|
readonly text: string
|
||||||
readonly files?: ReadonlyArray<{
|
readonly files?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery: "steer" | "queue"
|
readonly delivery: "steer" | "queue"
|
||||||
|
|
@ -934,16 +934,16 @@ export type SessionContextOutput = {
|
||||||
readonly time: { readonly created: number }
|
readonly time: { readonly created: number }
|
||||||
readonly text: string
|
readonly text: string
|
||||||
readonly files?: ReadonlyArray<{
|
readonly files?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly type: "user"
|
readonly type: "user"
|
||||||
}
|
}
|
||||||
|
|
@ -1038,12 +1038,12 @@ export type SessionContextOutput = {
|
||||||
readonly status: "completed"
|
readonly status: "completed"
|
||||||
readonly input: { readonly [x: string]: JsonValue }
|
readonly input: { readonly [x: string]: JsonValue }
|
||||||
readonly attachments?: ReadonlyArray<{
|
readonly attachments?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly content: ReadonlyArray<
|
readonly content: ReadonlyArray<
|
||||||
| { readonly type: "text"; readonly text: string }
|
| { readonly type: "text"; readonly text: string }
|
||||||
|
|
@ -1198,16 +1198,16 @@ export type SessionLogOutput =
|
||||||
readonly prompt: {
|
readonly prompt: {
|
||||||
readonly text: string
|
readonly text: string
|
||||||
readonly files?: ReadonlyArray<{
|
readonly files?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery: "steer" | "queue"
|
readonly delivery: "steer" | "queue"
|
||||||
|
|
@ -1631,16 +1631,16 @@ export type SessionMessageOutput = {
|
||||||
readonly time: { readonly created: number }
|
readonly time: { readonly created: number }
|
||||||
readonly text: string
|
readonly text: string
|
||||||
readonly files?: ReadonlyArray<{
|
readonly files?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly type: "user"
|
readonly type: "user"
|
||||||
}
|
}
|
||||||
|
|
@ -1735,12 +1735,12 @@ export type SessionMessageOutput = {
|
||||||
readonly status: "completed"
|
readonly status: "completed"
|
||||||
readonly input: { readonly [x: string]: JsonValue }
|
readonly input: { readonly [x: string]: JsonValue }
|
||||||
readonly attachments?: ReadonlyArray<{
|
readonly attachments?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly content: ReadonlyArray<
|
readonly content: ReadonlyArray<
|
||||||
| { readonly type: "text"; readonly text: string }
|
| { readonly type: "text"; readonly text: string }
|
||||||
|
|
@ -1833,16 +1833,16 @@ export type MessageListOutput = {
|
||||||
readonly time: { readonly created: number }
|
readonly time: { readonly created: number }
|
||||||
readonly text: string
|
readonly text: string
|
||||||
readonly files?: ReadonlyArray<{
|
readonly files?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly type: "user"
|
readonly type: "user"
|
||||||
}
|
}
|
||||||
|
|
@ -1937,12 +1937,12 @@ export type MessageListOutput = {
|
||||||
readonly status: "completed"
|
readonly status: "completed"
|
||||||
readonly input: { readonly [x: string]: JsonValue }
|
readonly input: { readonly [x: string]: JsonValue }
|
||||||
readonly attachments?: ReadonlyArray<{
|
readonly attachments?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly content: ReadonlyArray<
|
readonly content: ReadonlyArray<
|
||||||
| { readonly type: "text"; readonly text: string }
|
| { readonly type: "text"; readonly text: string }
|
||||||
|
|
@ -4485,16 +4485,16 @@ export type EventSubscribeOutput =
|
||||||
readonly prompt: {
|
readonly prompt: {
|
||||||
readonly text: string
|
readonly text: string
|
||||||
readonly files?: ReadonlyArray<{
|
readonly files?: ReadonlyArray<{
|
||||||
readonly uri: string
|
readonly data: string
|
||||||
readonly mime: string
|
readonly mime: string
|
||||||
readonly content?: string
|
readonly source: { readonly type: "inline" } | { readonly type: "uri"; readonly uri: string }
|
||||||
readonly name?: string
|
readonly name?: string
|
||||||
readonly description?: string
|
readonly description?: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
readonly agents?: ReadonlyArray<{
|
readonly agents?: ReadonlyArray<{
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source?: { readonly start: number; readonly end: number; readonly text: string }
|
readonly mention?: { readonly start: number; readonly end: number; readonly text: string }
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
readonly delivery: "steer" | "queue"
|
readonly delivery: "steer" | "queue"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import { ProjectV2 } from "@opencode-ai/core/project"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { SessionInput as CoreSessionInput } from "@opencode-ai/core/session/input"
|
import { SessionInput as CoreSessionInput } from "@opencode-ai/core/session/input"
|
||||||
import { SessionMessage as CoreSessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage as CoreSessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { Prompt as CorePrompt } from "@opencode-ai/core/session/prompt"
|
|
||||||
import { Agent } from "@opencode-ai/schema/agent"
|
import { Agent } from "@opencode-ai/schema/agent"
|
||||||
import { Location } from "@opencode-ai/schema/location"
|
import { Location } from "@opencode-ai/schema/location"
|
||||||
import { Model } from "@opencode-ai/schema/model"
|
import { Model } from "@opencode-ai/schema/model"
|
||||||
|
|
@ -32,7 +31,6 @@ test("Core and Server reuse the authoritative Schema and Protocol values", () =>
|
||||||
expect(ProjectV2.Directories).toBe(Project.Directories)
|
expect(ProjectV2.Directories).toBe(Project.Directories)
|
||||||
expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted)
|
expect(CoreSessionInput.Admitted).toBe(SessionInput.Admitted)
|
||||||
expect(CoreSessionMessage.Message).toBe(SessionMessage.Message)
|
expect(CoreSessionMessage.Message).toBe(SessionMessage.Message)
|
||||||
expect(CorePrompt).toBe(Prompt)
|
|
||||||
expect(Api.groups["server.session"].identifier).toBe("server.session")
|
expect(Api.groups["server.session"].identifier).toBe("server.session")
|
||||||
expect(Api.groups["server.project"].identifier).toBe("server.project")
|
expect(Api.groups["server.project"].identifier).toBe("server.project")
|
||||||
expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups))
|
expect(Object.keys(ClientApi.groups)).toEqual(Object.keys(Api.groups))
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,6 @@
|
||||||
export * as Mime from "./mime.js"
|
export * as Mime from "./mime.js"
|
||||||
|
|
||||||
import { Effect, FileSystem, Option } from "effect"
|
export function detect(bytes: Uint8Array) {
|
||||||
import { fileURLToPath } from "url"
|
|
||||||
import { FSUtil } from "./fs-util"
|
|
||||||
|
|
||||||
const SAMPLE_BYTES = 8192
|
|
||||||
|
|
||||||
export const resolve = Effect.fn("Mime.resolve")(function* (uri: string) {
|
|
||||||
const data = dataSample(uri)
|
|
||||||
if (data) return detect(data)
|
|
||||||
|
|
||||||
const target = yield* Effect.try({
|
|
||||||
try: () => localPath(uri),
|
|
||||||
catch: () => new Error("Invalid file URI"),
|
|
||||||
}).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
|
||||||
if (!target) return "application/octet-stream"
|
|
||||||
|
|
||||||
const fs = yield* FSUtil.Service
|
|
||||||
const local = yield* Effect.scoped(
|
|
||||||
Effect.gen(function* () {
|
|
||||||
const info = yield* fs.stat(target)
|
|
||||||
if (info.type === "Directory") return { type: "directory" as const }
|
|
||||||
if (info.type !== "File") return
|
|
||||||
const file = yield* fs.open(target)
|
|
||||||
return {
|
|
||||||
type: "file" as const,
|
|
||||||
sample: Option.getOrElse(yield* file.readAlloc(FileSystem.Size(SAMPLE_BYTES)), () => new Uint8Array()),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
|
||||||
|
|
||||||
if (local?.type === "directory") return "application/x-directory"
|
|
||||||
if (local?.type === "file") return detect(local.sample)
|
|
||||||
return "application/octet-stream"
|
|
||||||
})
|
|
||||||
|
|
||||||
function detect(bytes: Uint8Array) {
|
|
||||||
if (startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return "image/png"
|
if (startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return "image/png"
|
||||||
if (startsWith(bytes, [0xff, 0xd8, 0xff])) return "image/jpeg"
|
if (startsWith(bytes, [0xff, 0xd8, 0xff])) return "image/jpeg"
|
||||||
if (startsWith(bytes, [0x47, 0x49, 0x46, 0x38])) return "image/gif"
|
if (startsWith(bytes, [0x47, 0x49, 0x46, 0x38])) return "image/gif"
|
||||||
|
|
@ -52,25 +17,6 @@ function detect(bytes: Uint8Array) {
|
||||||
return isText(bytes) ? "text/plain" : "application/octet-stream"
|
return isText(bytes) ? "text/plain" : "application/octet-stream"
|
||||||
}
|
}
|
||||||
|
|
||||||
function dataSample(uri: string) {
|
|
||||||
if (!uri.startsWith("data:")) return
|
|
||||||
const comma = uri.indexOf(",")
|
|
||||||
if (comma === -1) return new Uint8Array()
|
|
||||||
const metadata = uri.slice(5, comma)
|
|
||||||
const payload = uri.slice(comma + 1)
|
|
||||||
if (metadata.split(";").some((part) => part.toLowerCase() === "base64")) {
|
|
||||||
return Buffer.from(payload.slice(0, Math.ceil((SAMPLE_BYTES * 4) / 3) + 4), "base64").subarray(0, SAMPLE_BYTES)
|
|
||||||
}
|
|
||||||
return new TextEncoder().encode(payload.slice(0, SAMPLE_BYTES))
|
|
||||||
}
|
|
||||||
|
|
||||||
function localPath(uri: string) {
|
|
||||||
if (!URL.canParse(uri)) return
|
|
||||||
const url = new URL(uri)
|
|
||||||
if (url.protocol !== "file:") return
|
|
||||||
return fileURLToPath(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
function startsWith(bytes: Uint8Array, prefix: number[]) {
|
function startsWith(bytes: Uint8Array, prefix: number[]) {
|
||||||
return prefix.every((value, index) => bytes[index] === value)
|
return prefix.every((value, index) => bytes[index] === value)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { WorkspaceV2 } from "./workspace"
|
||||||
import { ModelV2 } from "./model"
|
import { ModelV2 } from "./model"
|
||||||
import { Location } from "./location"
|
import { Location } from "./location"
|
||||||
import { SessionMessage } from "./session/message"
|
import { SessionMessage } from "./session/message"
|
||||||
import { Prompt } from "./session/prompt"
|
import { Base64, FileAttachment, Prompt } from "@opencode-ai/schema/prompt"
|
||||||
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||||
import { EventV2 } from "./event"
|
import { EventV2 } from "./event"
|
||||||
import { Database } from "./database/database"
|
import { Database } from "./database/database"
|
||||||
|
|
@ -121,6 +121,10 @@ export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictE
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
messageID: SessionMessage.ID,
|
messageID: SessionMessage.ID,
|
||||||
}) {}
|
}) {}
|
||||||
|
export class AttachmentError extends Schema.TaggedErrorClass<AttachmentError>()("Session.AttachmentError", {
|
||||||
|
uri: Schema.String,
|
||||||
|
message: Schema.String,
|
||||||
|
}) {}
|
||||||
export class BusyError extends Schema.TaggedErrorClass<BusyError>()("Session.BusyError", {
|
export class BusyError extends Schema.TaggedErrorClass<BusyError>()("Session.BusyError", {
|
||||||
sessionID: SessionSchema.ID,
|
sessionID: SessionSchema.ID,
|
||||||
}) {}
|
}) {}
|
||||||
|
|
@ -135,6 +139,7 @@ export type Error =
|
||||||
| MessageDecodeError
|
| MessageDecodeError
|
||||||
| OperationUnavailableError
|
| OperationUnavailableError
|
||||||
| PromptConflictError
|
| PromptConflictError
|
||||||
|
| AttachmentError
|
||||||
| BusyError
|
| BusyError
|
||||||
| SkillNotFoundError
|
| SkillNotFoundError
|
||||||
| CommandV2.NotFoundError
|
| CommandV2.NotFoundError
|
||||||
|
|
@ -189,7 +194,7 @@ export interface Interface {
|
||||||
prompt: PromptInput.Prompt
|
prompt: PromptInput.Prompt
|
||||||
delivery?: SessionInput.Delivery
|
delivery?: SessionInput.Delivery
|
||||||
resume?: boolean
|
resume?: boolean
|
||||||
}) => Effect.Effect<SessionInput.Admitted, NotFoundError | PromptConflictError>
|
}) => Effect.Effect<SessionInput.Admitted, NotFoundError | PromptConflictError | AttachmentError>
|
||||||
readonly command: (input: {
|
readonly command: (input: {
|
||||||
id?: SessionMessage.ID
|
id?: SessionMessage.ID
|
||||||
sessionID: SessionSchema.ID
|
sessionID: SessionSchema.ID
|
||||||
|
|
@ -203,7 +208,7 @@ export interface Interface {
|
||||||
resume?: boolean
|
resume?: boolean
|
||||||
}) => Effect.Effect<
|
}) => Effect.Effect<
|
||||||
SessionInput.Admitted,
|
SessionInput.Admitted,
|
||||||
NotFoundError | PromptConflictError | CommandV2.NotFoundError | CommandV2.EvaluationError
|
NotFoundError | PromptConflictError | AttachmentError | CommandV2.NotFoundError | CommandV2.EvaluationError
|
||||||
>
|
>
|
||||||
readonly shell: (input: {
|
readonly shell: (input: {
|
||||||
id?: EventV2.ID
|
id?: EventV2.ID
|
||||||
|
|
@ -721,40 +726,98 @@ const resolvePrompt = Effect.fn("V2Session.resolvePrompt")(function* (input: Pro
|
||||||
const files = input.files
|
const files = input.files
|
||||||
? yield* Effect.forEach(
|
? yield* Effect.forEach(
|
||||||
input.files,
|
input.files,
|
||||||
(file) =>
|
(file) => materializeAttachment(fs, file),
|
||||||
Effect.gen(function* () {
|
|
||||||
const mime = yield* Mime.resolve(file.uri)
|
|
||||||
const content = mime === "text/plain" ? yield* readTextAttachment(fs, file.uri) : undefined
|
|
||||||
return { ...file, mime, ...(content === undefined ? {} : { content }) }
|
|
||||||
}),
|
|
||||||
{ concurrency: 8 },
|
{ concurrency: 8 },
|
||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
return Prompt.make({ text: input.text, agents: input.agents, files })
|
return Prompt.make({ text: input.text, agents: input.agents, files })
|
||||||
})
|
})
|
||||||
|
|
||||||
function readTextAttachment(fs: FSUtil.Interface, uri: string) {
|
const MAX_ATTACHMENT_BYTES = 20 * 1024 * 1024
|
||||||
if (uri.startsWith("data:")) return Effect.succeed(undefined)
|
|
||||||
return Effect.try({
|
const materializeAttachment = Effect.fn("V2Session.materializeAttachment")(function* (
|
||||||
|
fs: FSUtil.Interface,
|
||||||
|
input: PromptInput.FileAttachment,
|
||||||
|
) {
|
||||||
|
const resolved = input.uri.startsWith("data:")
|
||||||
|
? {
|
||||||
|
bytes: yield* decodeDataURL(input.uri),
|
||||||
|
source: { type: "inline" as const },
|
||||||
|
start: undefined,
|
||||||
|
end: undefined,
|
||||||
|
name: undefined,
|
||||||
|
}
|
||||||
|
: yield* readFileAttachment(fs, input.uri)
|
||||||
|
if (resolved.bytes.byteLength > MAX_ATTACHMENT_BYTES)
|
||||||
|
return yield* new AttachmentError({
|
||||||
|
uri: input.uri,
|
||||||
|
message: `Attachment exceeds the ${MAX_ATTACHMENT_BYTES} byte limit: ${input.uri}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mime = Mime.detect(resolved.bytes)
|
||||||
|
const content =
|
||||||
|
mime === "text/plain" && resolved.start !== undefined
|
||||||
|
? Buffer.from(
|
||||||
|
Buffer.from(resolved.bytes).toString("utf8").split("\n").slice(resolved.start - 1, resolved.end).join("\n"),
|
||||||
|
)
|
||||||
|
: resolved.bytes
|
||||||
|
return FileAttachment.create({
|
||||||
|
data: Base64.make(Buffer.from(content).toString("base64")),
|
||||||
|
mime,
|
||||||
|
source: resolved.source,
|
||||||
|
name: input.name ?? resolved.name,
|
||||||
|
description: input.description,
|
||||||
|
mention: input.mention,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const readFileAttachment = Effect.fn("V2Session.readFileAttachment")(function* (fs: FSUtil.Interface, uri: string) {
|
||||||
|
const url = yield* Effect.try({
|
||||||
try: () => new URL(uri),
|
try: () => new URL(uri),
|
||||||
catch: () => new Error("Invalid attachment URI"),
|
catch: () => new AttachmentError({ uri, message: `Invalid attachment URI: ${uri}` }),
|
||||||
}).pipe(
|
})
|
||||||
Effect.flatMap((url) => {
|
if (url.protocol !== "file:")
|
||||||
if (url.protocol !== "file:") return Effect.succeed(undefined)
|
return yield* new AttachmentError({ uri, message: `Unsupported attachment URI: ${uri}` })
|
||||||
const start = positiveInt(url.searchParams.get("start"))
|
const start = positiveInt(url.searchParams.get("start"))
|
||||||
const end = positiveInt(url.searchParams.get("end"))
|
const end = positiveInt(url.searchParams.get("end"))
|
||||||
|
const target = yield* Effect.try({
|
||||||
|
try: () => {
|
||||||
url.search = ""
|
url.search = ""
|
||||||
url.hash = ""
|
url.hash = ""
|
||||||
return Effect.try({
|
return fileURLToPath(url)
|
||||||
try: () => fileURLToPath(url),
|
},
|
||||||
catch: () => new Error("Invalid file URI"),
|
catch: () => new AttachmentError({ uri, message: `Invalid file URI: ${uri}` }),
|
||||||
}).pipe(
|
})
|
||||||
Effect.flatMap((target) => fs.readFileString(target)),
|
const info = yield* fs.stat(target).pipe(
|
||||||
Effect.map((content) => (start === undefined ? content : content.split("\n").slice(start - 1, end).join("\n"))),
|
Effect.mapError(() => new AttachmentError({ uri, message: `Unable to read attachment: ${uri}` })),
|
||||||
)
|
|
||||||
}),
|
|
||||||
Effect.catch(() => Effect.succeed(undefined)),
|
|
||||||
)
|
)
|
||||||
|
if (info.type !== "File") return yield* new AttachmentError({ uri, message: `Attachment is not a file: ${uri}` })
|
||||||
|
if (Number(info.size) > MAX_ATTACHMENT_BYTES)
|
||||||
|
return yield* new AttachmentError({
|
||||||
|
uri,
|
||||||
|
message: `Attachment exceeds the ${MAX_ATTACHMENT_BYTES} byte limit: ${uri}`,
|
||||||
|
})
|
||||||
|
const bytes = yield* fs.readFile(target).pipe(
|
||||||
|
Effect.mapError(() => new AttachmentError({ uri, message: `Unable to read attachment: ${uri}` })),
|
||||||
|
)
|
||||||
|
return { bytes, source: { type: "uri" as const, uri }, start, end, name: path.basename(target) }
|
||||||
|
})
|
||||||
|
|
||||||
|
function decodeDataURL(uri: string) {
|
||||||
|
return Effect.try({
|
||||||
|
try: () => {
|
||||||
|
const comma = uri.indexOf(",")
|
||||||
|
if (comma === -1) throw new Error("Invalid data URL")
|
||||||
|
const metadata = uri.slice(5, comma)
|
||||||
|
const payload = uri.slice(comma + 1)
|
||||||
|
if (!metadata.split(";").some((part) => part.toLowerCase() === "base64"))
|
||||||
|
return Buffer.from(decodeURIComponent(payload))
|
||||||
|
const bytes = Buffer.from(payload, "base64")
|
||||||
|
if (bytes.toString("base64") !== payload) throw new Error("Non-canonical base64")
|
||||||
|
return bytes
|
||||||
|
},
|
||||||
|
catch: () => new AttachmentError({ uri, message: "Invalid attachment data URL" }),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function positiveInt(value: string | null) {
|
function positiveInt(value: string | null) {
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,11 @@ export const serializeToolContent = (content: SessionMessage.ToolStateCompleted[
|
||||||
|
|
||||||
const serialize = (message: SessionMessage.Message) => {
|
const serialize = (message: SessionMessage.Message) => {
|
||||||
if (message.type === "user") {
|
if (message.type === "user") {
|
||||||
const files = message.files?.map((file) => `[Attached ${file.mime}: ${file.name ?? file.uri}]`) ?? []
|
const files =
|
||||||
|
message.files?.map(
|
||||||
|
(file) =>
|
||||||
|
`[Attached ${file.mime}: ${file.name ?? (file.source.type === "uri" ? file.source.uri : "inline attachment")}]`,
|
||||||
|
) ?? []
|
||||||
return [`[User]: ${message.text}`, ...files].join("\n")
|
return [`[User]: ${message.text}`, ...files].join("\n")
|
||||||
}
|
}
|
||||||
if (message.type === "assistant") {
|
if (message.type === "assistant") {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import type { Database } from "../database/database"
|
||||||
import type { EventV2 } from "../event"
|
import type { EventV2 } from "../event"
|
||||||
import { SessionEvent } from "./event"
|
import { SessionEvent } from "./event"
|
||||||
import { SessionMessage } from "./message"
|
import { SessionMessage } from "./message"
|
||||||
import { Prompt } from "./prompt"
|
import { Prompt } from "@opencode-ai/schema/prompt"
|
||||||
import { SessionSchema } from "./schema"
|
import { SessionSchema } from "./schema"
|
||||||
import { SessionInputTable, SessionMessageTable } from "./sql"
|
import { SessionInputTable, SessionMessageTable } from "./sql"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
export { AgentAttachment, FileAttachment, Prompt, Source } from "@opencode-ai/schema/prompt"
|
|
||||||
|
|
@ -9,12 +9,14 @@ import {
|
||||||
} from "@opencode-ai/llm"
|
} from "@opencode-ai/llm"
|
||||||
import { Option, Schema } from "effect"
|
import { Option, Schema } from "effect"
|
||||||
import { SessionMessage } from "../message"
|
import { SessionMessage } from "../message"
|
||||||
import type { FileAttachment } from "../prompt"
|
import type { FileAttachment } from "@opencode-ai/schema/prompt"
|
||||||
|
|
||||||
|
const imageMimes = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"])
|
||||||
|
|
||||||
const media = (file: FileAttachment): ContentPart => ({
|
const media = (file: FileAttachment): ContentPart => ({
|
||||||
type: "media",
|
type: "media",
|
||||||
mediaType: file.mime,
|
mediaType: file.mime,
|
||||||
data: file.uri,
|
data: file.data,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
metadata: file.description === undefined ? undefined : { description: file.description },
|
metadata: file.description === undefined ? undefined : { description: file.description },
|
||||||
})
|
})
|
||||||
|
|
@ -23,37 +25,22 @@ const textAttachment = (file: FileAttachment) =>
|
||||||
Message.make({
|
Message.make({
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [
|
content: [
|
||||||
`Attached file: ${file.name ?? file.uri}`,
|
`Attached file: ${file.name ?? (file.source.type === "uri" ? file.source.uri : "inline attachment")}`,
|
||||||
`Source: ${file.uri}`,
|
|
||||||
file.description === undefined ? undefined : `Description: ${file.description}`,
|
file.description === undefined ? undefined : `Description: ${file.description}`,
|
||||||
"",
|
"",
|
||||||
file.content ?? readTextData(file.uri) ?? "[Attachment content unavailable]",
|
Buffer.from(file.data, "base64").toString("utf8"),
|
||||||
]
|
]
|
||||||
.filter((line): line is string => line !== undefined)
|
.filter((line): line is string => line !== undefined)
|
||||||
.join("\n"),
|
.join("\n"),
|
||||||
metadata: {
|
metadata: {
|
||||||
attachment: {
|
attachment: {
|
||||||
uri: file.uri,
|
source: file.source,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
description: file.description,
|
description: file.description,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function readTextData(uri: string) {
|
|
||||||
if (!uri.startsWith("data:")) return
|
|
||||||
const comma = uri.indexOf(",")
|
|
||||||
if (comma === -1) return
|
|
||||||
const metadata = uri.slice(5, comma)
|
|
||||||
const payload = uri.slice(comma + 1)
|
|
||||||
if (metadata.split(";").some((part) => part.toLowerCase() === "base64")) return Buffer.from(payload, "base64").toString("utf8")
|
|
||||||
try {
|
|
||||||
return decodeURIComponent(payload)
|
|
||||||
} catch {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString)
|
const decodeToolInput = Schema.decodeUnknownOption(Schema.UnknownFromJsonString)
|
||||||
|
|
||||||
const toolInput = (tool: SessionMessage.AssistantTool) =>
|
const toolInput = (tool: SessionMessage.AssistantTool) =>
|
||||||
|
|
@ -160,7 +147,10 @@ function toLLMMessage(message: SessionMessage.Message, model: Model): Message[]
|
||||||
Message.make({
|
Message.make({
|
||||||
id: message.id,
|
id: message.id,
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [{ type: "text", text: message.text }, ...files.filter((file) => file.mime !== "text/plain").map(media)],
|
content: [
|
||||||
|
{ type: "text", text: message.text },
|
||||||
|
...files.filter((file) => imageMimes.has(file.mime)).map(media),
|
||||||
|
],
|
||||||
metadata: {
|
metadata: {
|
||||||
...message.metadata,
|
...message.metadata,
|
||||||
...(message.agents?.length ? { agents: message.agents } : {}),
|
...(message.agents?.length ? { agents: message.agents } : {}),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { sqliteTable, text, integer, index, primaryKey, real, uniqueIndex } from
|
||||||
import { directoryColumn, pathColumn } from "../database/path"
|
import { directoryColumn, pathColumn } from "../database/path"
|
||||||
import { ProjectTable } from "../project/sql"
|
import { ProjectTable } from "../project/sql"
|
||||||
import type { SessionMessage } from "./message"
|
import type { SessionMessage } from "./message"
|
||||||
import type { Prompt } from "./prompt"
|
import type { Prompt } from "@opencode-ai/schema/prompt"
|
||||||
import type { SessionInput } from "./input"
|
import type { SessionInput } from "./input"
|
||||||
import type { Snapshot } from "../snapshot"
|
import type { Snapshot } from "../snapshot"
|
||||||
import { PermissionV1 } from "../v1/permission"
|
import { PermissionV1 } from "../v1/permission"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { Prompt } from "@opencode-ai/schema/prompt"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
import { SessionV1 } from "@opencode-ai/core/v1/session"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
|
|
@ -192,7 +192,7 @@ describe("SessionV2.create", () => {
|
||||||
const parent = yield* session.create({ location, title: "Parent" })
|
const parent = yield* session.create({ location, title: "Parent" })
|
||||||
const admitted = yield* session.prompt({
|
const admitted = yield* session.prompt({
|
||||||
sessionID: parent.id,
|
sessionID: parent.id,
|
||||||
prompt: Prompt.make({ text: "First" }),
|
prompt: PromptInput.Prompt.make({ text: "First" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||||
|
|
@ -224,9 +224,9 @@ describe("SessionV2.create", () => {
|
||||||
promotedSeq: 2,
|
promotedSeq: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
yield* session.prompt({ sessionID: parent.id, prompt: Prompt.make({ text: "Parent changed" }), resume: false })
|
yield* session.prompt({ sessionID: parent.id, prompt: PromptInput.Prompt.make({ text: "Parent changed" }), resume: false })
|
||||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||||
yield* session.prompt({ sessionID: forked.id, prompt: Prompt.make({ text: "Child continues" }), resume: false })
|
yield* session.prompt({ sessionID: forked.id, prompt: PromptInput.Prompt.make({ text: "Child continues" }), resume: false })
|
||||||
yield* SessionInput.promoteSteers(db, events, forked.id)
|
yield* SessionInput.promoteSteers(db, events, forked.id)
|
||||||
|
|
||||||
expect((yield* session.context(parent.id)).map((message) => message.type)).toEqual(["user", "synthetic", "user"])
|
expect((yield* session.context(parent.id)).map((message) => message.type)).toEqual(["user", "synthetic", "user"])
|
||||||
|
|
@ -249,13 +249,13 @@ describe("SessionV2.create", () => {
|
||||||
const parent = yield* session.create({ location })
|
const parent = yield* session.create({ location })
|
||||||
const first = yield* session.prompt({
|
const first = yield* session.prompt({
|
||||||
sessionID: parent.id,
|
sessionID: parent.id,
|
||||||
prompt: Prompt.make({ text: "First" }),
|
prompt: PromptInput.Prompt.make({ text: "First" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||||
const second = yield* session.prompt({
|
const second = yield* session.prompt({
|
||||||
sessionID: parent.id,
|
sessionID: parent.id,
|
||||||
prompt: Prompt.make({ text: "Second" }),
|
prompt: PromptInput.Prompt.make({ text: "Second" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* SessionInput.promoteSteers(db, events, parent.id)
|
yield* SessionInput.promoteSteers(db, events, parent.id)
|
||||||
|
|
@ -373,7 +373,7 @@ describe("SessionV2.create", () => {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const created = yield* session.create({ location })
|
const created = yield* session.create({ location })
|
||||||
yield* session.prompt({ sessionID: created.id, prompt: Prompt.make({ text: "Hello" }), resume: false })
|
yield* session.prompt({ sessionID: created.id, prompt: PromptInput.Prompt.make({ text: "Hello" }), resume: false })
|
||||||
yield* SessionInput.promoteSteers(db, events, created.id)
|
yield* SessionInput.promoteSteers(db, events, created.id)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -393,7 +393,7 @@ describe("SessionV2.create", () => {
|
||||||
const created = yield* session.create({ id: SessionV2.ID.make("ses_fresh_target_replay"), location })
|
const created = yield* session.create({ id: SessionV2.ID.make("ses_fresh_target_replay"), location })
|
||||||
const admitted = yield* session.prompt({
|
const admitted = yield* session.prompt({
|
||||||
sessionID: created.id,
|
sessionID: created.id,
|
||||||
prompt: Prompt.make({ text: "Replay lifecycle" }),
|
prompt: PromptInput.Prompt.make({ text: "Replay lifecycle" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* SessionInput.promoteSteers(sourceDb, sourceEvents, created.id)
|
yield* SessionInput.promoteSteers(sourceDb, sourceEvents, created.id)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { Prompt } from "@opencode-ai/schema/prompt"
|
||||||
import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater"
|
import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { describe, expect } from "bun:test"
|
import { describe, expect } from "bun:test"
|
||||||
import { DateTime, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
import { DateTime, Effect, Fiber, Layer, Schema, Stream } from "effect"
|
||||||
|
import { mkdtemp, rm } from "fs/promises"
|
||||||
|
import { tmpdir } from "os"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { pathToFileURL } from "url"
|
import { pathToFileURL } from "url"
|
||||||
import { eq } from "drizzle-orm"
|
import { eq } from "drizzle-orm"
|
||||||
|
|
@ -15,7 +17,7 @@ import { Project } from "@opencode-ai/core/project"
|
||||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
|
|
@ -171,7 +173,7 @@ describe("SessionV2.prompt", () => {
|
||||||
|
|
||||||
const message = yield* session.prompt({
|
const message = yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Fix the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -195,7 +197,7 @@ describe("SessionV2.prompt", () => {
|
||||||
|
|
||||||
const boundary = yield* session.prompt({
|
const boundary = yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "boundary" }),
|
prompt: PromptInput.Prompt.make({ text: "boundary" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* SessionInput.promoteSteers(db, events, sessionID)
|
yield* SessionInput.promoteSteers(db, events, sessionID)
|
||||||
|
|
@ -207,7 +209,7 @@ describe("SessionV2.prompt", () => {
|
||||||
})
|
})
|
||||||
expect((yield* session.get(sessionID)).revert?.messageID).toBe(boundary.id)
|
expect((yield* session.get(sessionID)).revert?.messageID).toBe(boundary.id)
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "after revert" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "after revert" }), resume: false })
|
||||||
|
|
||||||
expect((yield* session.get(sessionID)).revert).toBeUndefined()
|
expect((yield* session.get(sessionID)).revert).toBeUndefined()
|
||||||
expect(
|
expect(
|
||||||
|
|
@ -230,17 +232,25 @@ describe("SessionV2.prompt", () => {
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: {
|
prompt: {
|
||||||
text: "Inspect this image",
|
text: "Inspect this image",
|
||||||
files: [{ uri, name: "image.png" }],
|
files: [{ uri, name: "image.png", mention: { start: 8, end: 17, text: "[Image 1]" } }],
|
||||||
},
|
},
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(message.prompt.files).toEqual([{ uri, name: "image.png", mime: "image/png" }])
|
expect(message.prompt.files).toEqual([
|
||||||
|
{
|
||||||
|
data: uri.slice(uri.indexOf(",") + 1),
|
||||||
|
mime: "image/png",
|
||||||
|
source: { type: "inline" },
|
||||||
|
name: "image.png",
|
||||||
|
mention: { start: 8, end: 17, text: "[Image 1]" },
|
||||||
|
},
|
||||||
|
])
|
||||||
expect((yield* admitted(message.id))?.prompt.files).toEqual(message.prompt.files)
|
expect((yield* admitted(message.id))?.prompt.files).toEqual(message.prompt.files)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("classifies source files and directories from local content", () =>
|
it.effect("materializes selected source file content", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
|
|
@ -253,27 +263,78 @@ describe("SessionV2.prompt", () => {
|
||||||
const message = yield* session.prompt({
|
const message = yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: {
|
prompt: {
|
||||||
text: "Inspect these",
|
text: "Inspect this",
|
||||||
files: [
|
files: [{ uri: sourceUri.href, name: "main.ts" }],
|
||||||
{ uri: sourceUri.href, name: "main.ts" },
|
|
||||||
{ uri: pathToFileURL(directory).href, name: "source" },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(message.prompt.files).toEqual([
|
expect(message.prompt.files).toEqual([
|
||||||
{
|
{
|
||||||
uri: sourceUri.href,
|
data: Buffer.from('import { describe, expect } from "bun:test"').toString("base64"),
|
||||||
name: "main.ts",
|
|
||||||
mime: "text/plain",
|
mime: "text/plain",
|
||||||
content: 'import { describe, expect } from "bun:test"',
|
source: { type: "uri", uri: sourceUri.href },
|
||||||
|
name: "main.ts",
|
||||||
},
|
},
|
||||||
{ uri: pathToFileURL(directory).href, name: "source", mime: "application/x-directory" },
|
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("rejects directories as file attachments", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* setup
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const uri = pathToFileURL(import.meta.dir).href
|
||||||
|
|
||||||
|
const error = yield* session
|
||||||
|
.prompt({
|
||||||
|
sessionID,
|
||||||
|
prompt: { text: "Inspect this", files: [{ uri, name: "source" }] },
|
||||||
|
resume: false,
|
||||||
|
})
|
||||||
|
.pipe(Effect.flip)
|
||||||
|
|
||||||
|
expect(error).toMatchObject({
|
||||||
|
_tag: "Session.AttachmentError",
|
||||||
|
uri,
|
||||||
|
message: `Attachment is not a file: ${uri}`,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("materializes local image content before admission", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* setup
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const directory = yield* Effect.acquireRelease(
|
||||||
|
Effect.promise(() => mkdtemp(path.join(tmpdir(), "opencode-session-prompt-"))),
|
||||||
|
(directory) => Effect.promise(() => rm(directory, { recursive: true, force: true })),
|
||||||
|
)
|
||||||
|
const source = path.join(directory, "image.png")
|
||||||
|
const bytes = Buffer.from(
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
|
||||||
|
"base64",
|
||||||
|
)
|
||||||
|
yield* Effect.promise(() => Bun.write(source, bytes))
|
||||||
|
|
||||||
|
const message = yield* session.prompt({
|
||||||
|
sessionID,
|
||||||
|
prompt: { text: "Inspect this image", files: [{ uri: pathToFileURL(source).href }] },
|
||||||
|
resume: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(message.prompt.files).toEqual([
|
||||||
|
{
|
||||||
|
data: bytes.toString("base64"),
|
||||||
|
mime: "image/png",
|
||||||
|
source: { type: "uri", uri: pathToFileURL(source).href },
|
||||||
|
name: "image.png",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect((yield* admitted(message.id))?.prompt.files).toEqual(message.prompt.files)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
it.effect("sniffs data URL content instead of trusting its declared MIME", () =>
|
it.effect("sniffs data URL content instead of trusting its declared MIME", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
|
|
@ -286,7 +347,36 @@ describe("SessionV2.prompt", () => {
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(message.prompt.files).toEqual([{ uri, name: "main.ts", mime: "text/plain" }])
|
expect(message.prompt.files).toEqual([
|
||||||
|
{
|
||||||
|
data: Buffer.from("export const value = 1\n").toString("base64"),
|
||||||
|
mime: "text/plain",
|
||||||
|
source: { type: "inline" },
|
||||||
|
name: "main.ts",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
it.effect("rejects malformed base64 data URLs", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
yield* setup
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const uri = "data:image/png;base64,not-base64"
|
||||||
|
|
||||||
|
const error = yield* session
|
||||||
|
.prompt({
|
||||||
|
sessionID,
|
||||||
|
prompt: { text: "Inspect this", files: [{ uri, name: "image.png" }] },
|
||||||
|
resume: false,
|
||||||
|
})
|
||||||
|
.pipe(Effect.flip)
|
||||||
|
|
||||||
|
expect(error).toMatchObject({
|
||||||
|
_tag: "Session.AttachmentError",
|
||||||
|
uri,
|
||||||
|
message: "Invalid attachment data URL",
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -303,8 +393,8 @@ describe("SessionV2.prompt", () => {
|
||||||
const fiber = yield* publicEvents({ sessionID }).pipe(Stream.take(4), Stream.runCollect, Effect.forkScoped)
|
const fiber = yield* publicEvents({ sessionID }).pipe(Stream.take(4), Stream.runCollect, Effect.forkScoped)
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* SessionInput.promoteSteers(db, events, sessionID)
|
yield* SessionInput.promoteSteers(db, events, sessionID)
|
||||||
const streamed = Array.from(yield* Fiber.join(fiber))
|
const streamed = Array.from(yield* Fiber.join(fiber))
|
||||||
|
|
||||||
|
|
@ -328,7 +418,7 @@ describe("SessionV2.prompt", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const message = yield* session.prompt({
|
const message = yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Fix the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -347,7 +437,7 @@ describe("SessionV2.prompt", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const input = { sessionID, prompt: Prompt.make({ text: "Fix the failing tests" }), resume: false }
|
const input = { sessionID, prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }), resume: false }
|
||||||
|
|
||||||
const first = yield* session.prompt(input)
|
const first = yield* session.prompt(input)
|
||||||
const second = yield* session.prompt(input)
|
const second = yield* session.prompt(input)
|
||||||
|
|
@ -365,7 +455,7 @@ describe("SessionV2.prompt", () => {
|
||||||
const input = {
|
const input = {
|
||||||
sessionID,
|
sessionID,
|
||||||
id: messageID,
|
id: messageID,
|
||||||
prompt: Prompt.make({ text: "Fix the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,7 +475,7 @@ describe("SessionV2.prompt", () => {
|
||||||
const input = {
|
const input = {
|
||||||
sessionID,
|
sessionID,
|
||||||
id: messageID,
|
id: messageID,
|
||||||
prompt: Prompt.make({ text: "Recover committed prompt" }),
|
prompt: PromptInput.Prompt.make({ text: "Recover committed prompt" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
}
|
}
|
||||||
const first = yield* session.prompt(input)
|
const first = yield* session.prompt(input)
|
||||||
|
|
@ -406,13 +496,13 @@ describe("SessionV2.prompt", () => {
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
id: messageID,
|
id: messageID,
|
||||||
prompt: Prompt.make({ text: "Fix the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }),
|
||||||
})
|
})
|
||||||
const failure = yield* session
|
const failure = yield* session
|
||||||
.prompt({
|
.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
id: messageID,
|
id: messageID,
|
||||||
prompt: Prompt.make({ text: "Delete the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Delete the failing tests" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
.pipe(Effect.flip)
|
.pipe(Effect.flip)
|
||||||
|
|
@ -431,14 +521,14 @@ describe("SessionV2.prompt", () => {
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
id: messageID,
|
id: messageID,
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Fix the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
const failure = yield* session
|
const failure = yield* session
|
||||||
.prompt({
|
.prompt({
|
||||||
id: messageID,
|
id: messageID,
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Fix the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }),
|
||||||
delivery: "queue",
|
delivery: "queue",
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
@ -455,7 +545,7 @@ describe("SessionV2.prompt", () => {
|
||||||
const input = {
|
const input = {
|
||||||
sessionID,
|
sessionID,
|
||||||
id: messageID,
|
id: messageID,
|
||||||
prompt: Prompt.make({ text: "Fix the failing tests" }),
|
prompt: PromptInput.Prompt.make({ text: "Fix the failing tests" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -474,7 +564,7 @@ describe("SessionV2.prompt", () => {
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "Promote once" }), resume: false })
|
yield* session.prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "Promote once" }), resume: false })
|
||||||
|
|
||||||
yield* Effect.all(
|
yield* Effect.all(
|
||||||
[SessionInput.promoteSteers(db, events, sessionID), SessionInput.promoteSteers(db, events, sessionID)],
|
[SessionInput.promoteSteers(db, events, sessionID), SessionInput.promoteSteers(db, events, sessionID)],
|
||||||
|
|
@ -499,7 +589,7 @@ describe("SessionV2.prompt", () => {
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
id: messageID,
|
id: messageID,
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Replay pending" }),
|
prompt: PromptInput.Prompt.make({ text: "Replay pending" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
const recorded = yield* db
|
const recorded = yield* db
|
||||||
|
|
@ -552,7 +642,7 @@ describe("SessionV2.prompt", () => {
|
||||||
.onConflictDoNothing()
|
.onConflictDoNothing()
|
||||||
.run()
|
.run()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
const prompt = Prompt.make({ text: "Fix the failing tests" })
|
const prompt = PromptInput.Prompt.make({ text: "Fix the failing tests" })
|
||||||
|
|
||||||
yield* session.prompt({ id: messageID, sessionID, prompt, resume: false })
|
yield* session.prompt({ id: messageID, sessionID, prompt, resume: false })
|
||||||
const failure = yield* session
|
const failure = yield* session
|
||||||
|
|
@ -586,7 +676,7 @@ describe("SessionV2.prompt", () => {
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
|
|
||||||
const failure = yield* session
|
const failure = yield* session
|
||||||
.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "Conflicting prompt" }), resume: false })
|
.prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "Conflicting prompt" }), resume: false })
|
||||||
.pipe(Effect.flip)
|
.pipe(Effect.flip)
|
||||||
|
|
||||||
expect(failure).toMatchObject({ _tag: "Session.PromptConflictError", sessionID, messageID })
|
expect(failure).toMatchObject({ _tag: "Session.PromptConflictError", sessionID, messageID })
|
||||||
|
|
@ -601,7 +691,7 @@ describe("SessionV2.prompt", () => {
|
||||||
executionCalls.length = 0
|
executionCalls.length = 0
|
||||||
wakeCalls.length = 0
|
wakeCalls.length = 0
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run by default" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Run by default" }) })
|
||||||
|
|
||||||
expect(executionCalls).toEqual([])
|
expect(executionCalls).toEqual([])
|
||||||
expect(wakeCalls).toEqual([sessionID])
|
expect(wakeCalls).toEqual([sessionID])
|
||||||
|
|
@ -617,7 +707,7 @@ describe("SessionV2.prompt", () => {
|
||||||
|
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Run explicitly" }),
|
prompt: PromptInput.Prompt.make({ text: "Run explicitly" }),
|
||||||
resume: true,
|
resume: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -633,7 +723,7 @@ describe("SessionV2.prompt", () => {
|
||||||
executionCalls.length = 0
|
executionCalls.length = 0
|
||||||
wakeCalls.length = 0
|
wakeCalls.length = 0
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Do not run" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Do not run" }), resume: false })
|
||||||
|
|
||||||
expect(executionCalls).toEqual([])
|
expect(executionCalls).toEqual([])
|
||||||
expect(wakeCalls).toEqual([])
|
expect(wakeCalls).toEqual([])
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import * as OpenAIChat from "@opencode-ai/llm/protocols/openai-chat"
|
||||||
import { ModelV2 } from "@opencode-ai/core/model"
|
import { ModelV2 } from "@opencode-ai/core/model"
|
||||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { AgentAttachment, FileAttachment } from "@opencode-ai/core/session/prompt"
|
import { AgentAttachment, Base64, FileAttachment } from "@opencode-ai/schema/prompt"
|
||||||
import { toLLMMessages } from "@opencode-ai/core/session/runner/to-llm-message"
|
import { toLLMMessages } from "@opencode-ai/core/session/runner/to-llm-message"
|
||||||
import { SessionV2 } from "@opencode-ai/core/session"
|
import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { Shell } from "@opencode-ai/schema/shell"
|
import { Shell } from "@opencode-ai/schema/shell"
|
||||||
|
|
@ -49,7 +49,12 @@ describe("toLLMMessages", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("maps every top-level V2 Session message type", () => {
|
test("maps every top-level V2 Session message type", () => {
|
||||||
const file = FileAttachment.make({ uri: "data:image/png;base64,aGVsbG8=", mime: "image/png", name: "hello.png" })
|
const file = FileAttachment.make({
|
||||||
|
data: Base64.make("aGVsbG8="),
|
||||||
|
mime: "image/png",
|
||||||
|
source: { type: "inline" },
|
||||||
|
name: "hello.png",
|
||||||
|
})
|
||||||
const messages = toLLMMessages(
|
const messages = toLLMMessages(
|
||||||
[
|
[
|
||||||
SessionMessage.AgentSelected.make({
|
SessionMessage.AgentSelected.make({
|
||||||
|
|
@ -122,7 +127,7 @@ describe("toLLMMessages", () => {
|
||||||
role: "user",
|
role: "user",
|
||||||
content: [
|
content: [
|
||||||
{ type: "text", text: "Inspect this image" },
|
{ type: "text", text: "Inspect this image" },
|
||||||
{ type: "media", mediaType: "image/png", data: "data:image/png;base64,aGVsbG8=", filename: "hello.png" },
|
{ type: "media", mediaType: "image/png", data: "aGVsbG8=", filename: "hello.png" },
|
||||||
],
|
],
|
||||||
metadata: { agents: [{ name: "build" }] },
|
metadata: { agents: [{ name: "build" }] },
|
||||||
}),
|
}),
|
||||||
|
|
@ -151,9 +156,9 @@ Recent work
|
||||||
|
|
||||||
test("lowers text attachments as separate user messages", () => {
|
test("lowers text attachments as separate user messages", () => {
|
||||||
const file = FileAttachment.make({
|
const file = FileAttachment.make({
|
||||||
uri: "file:///project/main.ts",
|
data: Base64.make(Buffer.from("export const value = 1").toString("base64")),
|
||||||
mime: "text/plain",
|
mime: "text/plain",
|
||||||
content: "export const value = 1",
|
source: { type: "uri", uri: "file:///project/main.ts" },
|
||||||
name: "main.ts",
|
name: "main.ts",
|
||||||
})
|
})
|
||||||
const messages = toLLMMessages(
|
const messages = toLLMMessages(
|
||||||
|
|
@ -175,10 +180,10 @@ Recent work
|
||||||
content: [
|
content: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: "Attached file: main.ts\nSource: file:///project/main.ts\n\nexport const value = 1",
|
text: "Attached file: main.ts\n\nexport const value = 1",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
metadata: { attachment: { uri: "file:///project/main.ts", name: "main.ts" } },
|
metadata: { attachment: { source: file.source, name: "main.ts" } },
|
||||||
})
|
})
|
||||||
expect(messages[1]).toMatchObject({
|
expect(messages[1]).toMatchObject({
|
||||||
id: id("user-text-file"),
|
id: id("user-text-file"),
|
||||||
|
|
@ -187,15 +192,21 @@ Recent work
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test("derives text attachment content from durable data URLs", () => {
|
test("decodes inline text attachment content", () => {
|
||||||
const uri = `data:text/plain;base64,${Buffer.from("inline content").toString("base64")}`
|
|
||||||
const messages = toLLMMessages(
|
const messages = toLLMMessages(
|
||||||
[
|
[
|
||||||
SessionMessage.User.make({
|
SessionMessage.User.make({
|
||||||
id: id("user-data-file"),
|
id: id("user-data-file"),
|
||||||
type: "user",
|
type: "user",
|
||||||
text: "Review this file",
|
text: "Review this file",
|
||||||
files: [FileAttachment.make({ uri, mime: "text/plain", name: "inline.txt" })],
|
files: [
|
||||||
|
FileAttachment.make({
|
||||||
|
data: Base64.make(Buffer.from("inline content").toString("base64")),
|
||||||
|
mime: "text/plain",
|
||||||
|
source: { type: "inline" },
|
||||||
|
name: "inline.txt",
|
||||||
|
}),
|
||||||
|
],
|
||||||
time: { created },
|
time: { created },
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
@ -205,11 +216,40 @@ Recent work
|
||||||
expect(messages[0]?.content).toEqual([
|
expect(messages[0]?.content).toEqual([
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: `Attached file: inline.txt\nSource: ${uri}\n\ninline content`,
|
text: "Attached file: inline.txt\n\ninline content",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("uses materialized image data as provider media and drops unsupported attachments", () => {
|
||||||
|
const data = Base64.make("AAECAw==")
|
||||||
|
const messages = toLLMMessages(
|
||||||
|
[
|
||||||
|
SessionMessage.User.make({
|
||||||
|
id: id("user-local-image"),
|
||||||
|
type: "user",
|
||||||
|
text: "Inspect this image",
|
||||||
|
files: [
|
||||||
|
FileAttachment.make({ data, mime: "image/png", source: { type: "inline" }, name: "image.png" }),
|
||||||
|
FileAttachment.make({
|
||||||
|
data: Base64.make("JVBERg=="),
|
||||||
|
mime: "application/pdf",
|
||||||
|
source: { type: "inline" },
|
||||||
|
name: "document.pdf",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
time: { created },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
model,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(messages[0]?.content).toEqual([
|
||||||
|
{ type: "text", text: "Inspect this image" },
|
||||||
|
{ type: "media", mediaType: "image/png", data, filename: "image.png" },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
test("replays durable tool media into canonical tool messages without structured base64", () => {
|
test("replays durable tool media into canonical tool messages without structured base64", () => {
|
||||||
const messages = toLLMMessages(
|
const messages = toLLMMessages(
|
||||||
[
|
[
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { Snapshot } from "@opencode-ai/core/snapshot"
|
import { Snapshot } from "@opencode-ai/core/snapshot"
|
||||||
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||||
import { SessionTitle } from "@opencode-ai/core/session/title"
|
import { SessionTitle } from "@opencode-ai/core/session/title"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
|
import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
|
||||||
|
|
@ -172,7 +172,7 @@ describe("SessionRunnerLLM recorded", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const prompt = yield* session.prompt({
|
const prompt = yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Say hello in one short sentence." }),
|
prompt: PromptInput.Prompt.make({ text: "Say hello in one short sentence." }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
||||||
import { SessionTitle } from "@opencode-ai/core/session/title"
|
import { SessionTitle } from "@opencode-ai/core/session/title"
|
||||||
import { SessionInput } from "@opencode-ai/core/session/input"
|
import { SessionInput } from "@opencode-ai/core/session/input"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||||
import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
|
import { SessionRunCoordinator } from "@opencode-ai/core/session/run-coordinator"
|
||||||
|
|
@ -375,7 +375,7 @@ const setupOverflowRecovery = Effect.gen(function* () {
|
||||||
response = fragmentFixture("text", "text-earlier", ["Earlier answer"]).completeEvents
|
response = fragmentFixture("text", "text-earlier", ["Earlier answer"]).completeEvents
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Earlier question ".repeat(700) }),
|
prompt: PromptInput.Prompt.make({ text: "Earlier question ".repeat(700) }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
@ -516,7 +516,7 @@ const verifyEphemeralDeltas = (kind: FragmentKind) =>
|
||||||
const chunks = Array.from({ length: 32 }, (_, index) => `${index},`)
|
const chunks = Array.from({ length: 32 }, (_, index) => `${index},`)
|
||||||
const fixture = fragmentFixture(kind, fragmentID(kind, "many"), chunks)
|
const fixture = fragmentFixture(kind, fragmentID(kind, "many"), chunks)
|
||||||
const expectedContext = [{ type: "user", text: prompt }, fixture.expectedAssistant]
|
const expectedContext = [{ type: "user", text: prompt }, fixture.expectedAssistant]
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: prompt }), resume: false })
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const live = yield* events.subscribe(fixture.delta).pipe(Stream.take(32), Stream.runCollect, Effect.forkScoped)
|
const live = yield* events.subscribe(fixture.delta).pipe(Stream.take(32), Stream.runCollect, Effect.forkScoped)
|
||||||
yield* Effect.yieldNow
|
yield* Effect.yieldNow
|
||||||
|
|
@ -547,7 +547,7 @@ const verifyPartialFlushOnFailure = (kind: FragmentKind) =>
|
||||||
const prompt = `Fail after ${kind}`
|
const prompt = `Fail after ${kind}`
|
||||||
const fixture = fragmentFixture(kind, fragmentID(kind, "partial"), ["Partial"])
|
const fixture = fragmentFixture(kind, fragmentID(kind, "partial"), ["Partial"])
|
||||||
const failure = providerUnavailable()
|
const failure = providerUnavailable()
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: prompt }), resume: false })
|
||||||
responseStream = Stream.concat(Stream.fromIterable(fixture.partialEvents), Stream.fail(failure))
|
responseStream = Stream.concat(Stream.fromIterable(fixture.partialEvents), Stream.fail(failure))
|
||||||
|
|
||||||
expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure)
|
expect(yield* session.resume(sessionID).pipe(Effect.flip)).toBe(failure)
|
||||||
|
|
@ -569,7 +569,7 @@ const verifyPartialFlushOnInterruption = (kind: FragmentKind) =>
|
||||||
const prompt = `Interrupt after ${kind}`
|
const prompt = `Interrupt after ${kind}`
|
||||||
const fixture = fragmentFixture(kind, fragmentID(kind, "interrupted"), ["Partial"])
|
const fixture = fragmentFixture(kind, fragmentID(kind, "interrupted"), ["Partial"])
|
||||||
const streamed = yield* Deferred.make<void>()
|
const streamed = yield* Deferred.make<void>()
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: prompt }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: prompt }), resume: false })
|
||||||
responseStream = Stream.concat(
|
responseStream = Stream.concat(
|
||||||
Stream.fromIterable(fixture.partialEvents),
|
Stream.fromIterable(fixture.partialEvents),
|
||||||
Stream.fromEffect(Deferred.succeed(streamed, undefined)).pipe(Stream.flatMap(() => Stream.never)),
|
Stream.fromEffect(Deferred.succeed(streamed, undefined)).pipe(Stream.flatMap(() => Stream.never)),
|
||||||
|
|
@ -613,7 +613,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Use application context" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Use application context" }), resume: false })
|
||||||
responses = [
|
responses = [
|
||||||
[
|
[
|
||||||
LLMEvent.stepStart({ index: 0 }),
|
LLMEvent.stepStart({ index: 0 }),
|
||||||
|
|
@ -661,7 +661,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
streamStarted = undefined
|
streamStarted = undefined
|
||||||
response = []
|
response = []
|
||||||
|
|
||||||
const message = yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run automatically" }) })
|
const message = yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Run automatically" }) })
|
||||||
yield* session.wait(sessionID)
|
yield* session.wait(sessionID)
|
||||||
|
|
||||||
expect(requests).toHaveLength(1)
|
expect(requests).toHaveLength(1)
|
||||||
|
|
@ -675,8 +675,8 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = undefined
|
responses = undefined
|
||||||
|
|
@ -703,7 +703,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
const messageID = SessionMessage.ID.create()
|
const messageID = SessionMessage.ID.create()
|
||||||
systemUnavailable = true
|
systemUnavailable = true
|
||||||
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
|
|
||||||
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
||||||
|
|
@ -721,7 +721,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
).toBeUndefined()
|
).toBeUndefined()
|
||||||
|
|
||||||
systemUnavailable = false
|
systemUnavailable = false
|
||||||
yield* session.prompt({ id: messageID, sessionID, prompt: Prompt.make({ text: "First" }) })
|
yield* session.prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "First" }) })
|
||||||
yield* session.wait(sessionID)
|
yield* session.wait(sessionID)
|
||||||
|
|
||||||
expect(requests).toHaveLength(1)
|
expect(requests).toHaveLength(1)
|
||||||
|
|
@ -735,7 +735,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
@ -752,7 +752,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
.get(),
|
.get(),
|
||||||
).toBeUndefined()
|
).toBeUndefined()
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
const exit = yield* session.resume(sessionID).pipe(Effect.exit)
|
||||||
|
|
||||||
expect(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)).toBe(true)
|
expect(Exit.isFailure(exit) && Cause.hasInterruptsOnly(exit.cause)).toBe(true)
|
||||||
|
|
@ -766,7 +766,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
|
|
@ -795,7 +795,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
yield* db
|
yield* db
|
||||||
|
|
@ -804,7 +804,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
.where(eq(InstructionCheckpointTable.session_id, sessionID))
|
.where(eq(InstructionCheckpointTable.session_id, sessionID))
|
||||||
.run()
|
.run()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
|
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
@ -828,13 +828,13 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
systemBaseline = "Changed context"
|
systemBaseline = "Changed context"
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
||||||
|
|
@ -863,7 +863,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
currentModel = Model.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
|
currentModel = Model.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = fragmentFixture("text", "text-provider-prompt", ["Done"]).completeEvents
|
response = fragmentFixture("text", "text-provider-prompt", ["Done"]).completeEvents
|
||||||
|
|
@ -888,7 +888,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = fragmentFixture("text", "text-empty-agent-system", ["Done"]).completeEvents
|
response = fragmentFixture("text", "text-empty-agent-system", ["Done"]).completeEvents
|
||||||
|
|
@ -912,7 +912,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = fragmentFixture("text", "text-build", ["Done"]).completeEvents
|
response = fragmentFixture("text", "text-build", ["Done"]).completeEvents
|
||||||
|
|
@ -938,7 +938,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
editor.default(AgentV2.ID.make("reviewer"))
|
editor.default(AgentV2.ID.make("reviewer"))
|
||||||
})
|
})
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = fragmentFixture("text", "text-reviewer", ["Done"]).completeEvents
|
response = fragmentFixture("text", "text-reviewer", ["Done"]).completeEvents
|
||||||
|
|
@ -960,7 +960,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = fragmentFixture("text", "text-no-system", ["Done"]).completeEvents
|
response = fragmentFixture("text", "text-no-system", ["Done"]).completeEvents
|
||||||
|
|
@ -988,7 +988,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
.run()
|
.run()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = fragmentFixture("text", "text-selected", ["Done"]).completeEvents
|
response = fragmentFixture("text", "text-selected", ["Done"]).completeEvents
|
||||||
|
|
@ -1005,7 +1005,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
skillBaselines.set(AgentV2.ID.make("build"), "Build skills")
|
skillBaselines.set(AgentV2.ID.make("build"), "Build skills")
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
|
|
@ -1015,7 +1015,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
sessionID,
|
sessionID,
|
||||||
agent: "reviewer",
|
agent: "reviewer",
|
||||||
})
|
})
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
||||||
|
|
@ -1044,7 +1044,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
})
|
})
|
||||||
.pipe(Effect.asVoid)
|
.pipe(Effect.asVoid)
|
||||||
})
|
})
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
|
|
@ -1072,7 +1072,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
})
|
})
|
||||||
.pipe(Effect.asVoid)
|
.pipe(Effect.asVoid)
|
||||||
})
|
})
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
|
|
@ -1088,13 +1088,13 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
systemRemoved = true
|
systemRemoved = true
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "system", "user"])
|
expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "system", "user"])
|
||||||
|
|
@ -1111,7 +1111,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const contextEntries = yield* InstructionEntry.Service
|
const contextEntries = yield* InstructionEntry.Service
|
||||||
yield* contextEntries.put({ sessionID, key: "deploy-target", value: "production" })
|
yield* contextEntries.put({ sessionID, key: "deploy-target", value: "production" })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
|
|
@ -1125,7 +1125,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
// Non-string JSON pretty-prints; the change narrates as a System update.
|
// Non-string JSON pretty-prints; the change narrates as a System update.
|
||||||
yield* contextEntries.put({ sessionID, key: "deploy-target", value: { region: "us-east-1" } })
|
yield* contextEntries.put({ sessionID, key: "deploy-target", value: { region: "us-east-1" } })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "system", "user"])
|
expect(requests[1]?.messages.map((message) => message.role)).toEqual(["user", "system", "user"])
|
||||||
|
|
@ -1146,7 +1146,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
// Deleting the row announces removal through the stored removal text.
|
// Deleting the row announces removal through the stored removal text.
|
||||||
yield* contextEntries.remove({ sessionID, key: "deploy-target" })
|
yield* contextEntries.remove({ sessionID, key: "deploy-target" })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Third" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests[2]?.messages.map((message) => message.role)).toEqual(["user", "system", "user", "system", "user"])
|
expect(requests[2]?.messages.map((message) => message.role)).toEqual(["user", "system", "user", "system", "user"])
|
||||||
|
|
@ -1162,20 +1162,20 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
systemBaseline = "Changed context"
|
systemBaseline = "Changed context"
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
yield* events.publish(SessionEvent.ModelSelected, {
|
yield* events.publish(SessionEvent.ModelSelected, {
|
||||||
sessionID,
|
sessionID,
|
||||||
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
||||||
})
|
})
|
||||||
systemBaseline = "Replacement context"
|
systemBaseline = "Replacement context"
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Third" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
||||||
|
|
@ -1195,7 +1195,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
])
|
])
|
||||||
yield* replaySessionProjection(sessionID)
|
yield* replaySessionProjection(sessionID)
|
||||||
expect(yield* session.messages({ sessionID })).toHaveLength(6)
|
expect(yield* session.messages({ sessionID })).toHaveLength(6)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fourth" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fourth" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -1205,7 +1205,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
|
|
@ -1215,11 +1215,11 @@ describe("SessionRunnerLLM", () => {
|
||||||
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
model: { id: ModelV2.ID.make("replacement"), providerID: ProviderV2.ID.make("fake") },
|
||||||
})
|
})
|
||||||
systemUnavailable = true
|
systemUnavailable = true
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
systemUnavailable = false
|
systemUnavailable = false
|
||||||
systemBaseline = "Replacement context"
|
systemBaseline = "Replacement context"
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Third" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
||||||
|
|
@ -1235,7 +1235,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
|
|
@ -1252,7 +1252,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
recent: "",
|
recent: "",
|
||||||
})
|
})
|
||||||
systemBaseline = "Replacement context"
|
systemBaseline = "Replacement context"
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
expect(requests.map((request) => request.system.map((part) => part.text))).toEqual([
|
||||||
|
|
@ -1260,7 +1260,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
[defaultSystem, "Replacement context"],
|
[defaultSystem, "Replacement context"],
|
||||||
])
|
])
|
||||||
yield* replaySessionProjection(sessionID)
|
yield* replaySessionProjection(sessionID)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Third" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -1272,7 +1272,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
response = fragmentFixture("text", "text-first", ["Earlier answer"]).completeEvents
|
response = fragmentFixture("text", "text-first", ["Earlier answer"]).completeEvents
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Earlier question ".repeat(180) }),
|
prompt: PromptInput.Prompt.make({ text: "Earlier question ".repeat(180) }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
@ -1285,7 +1285,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
]
|
]
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Recent exact request ".repeat(180) }),
|
prompt: PromptInput.Prompt.make({ text: "Recent exact request ".repeat(180) }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
@ -1311,7 +1311,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
]
|
]
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Newest exact request ".repeat(180) }),
|
prompt: PromptInput.Prompt.make({ text: "Newest exact request ".repeat(180) }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
@ -1339,7 +1339,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
fragmentFixture("text", "text-summary", ["## Goal\n- Recover overflow"]).completeEvents,
|
fragmentFixture("text", "text-summary", ["## Goal\n- Recover overflow"]).completeEvents,
|
||||||
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
|
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
|
||||||
]
|
]
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests).toHaveLength(3)
|
expect(requests).toHaveLength(3)
|
||||||
|
|
@ -1369,7 +1369,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
fragmentFixture("text", "text-summary", ["## Goal\n- Recover once"]).completeEvents,
|
fragmentFixture("text", "text-summary", ["## Goal\n- Recover once"]).completeEvents,
|
||||||
overflow(),
|
overflow(),
|
||||||
]
|
]
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests).toHaveLength(3)
|
expect(requests).toHaveLength(3)
|
||||||
|
|
@ -1397,7 +1397,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
fragmentFixture("text", "text-summary", ["## Goal\n- Recover raw overflow"]).completeEvents,
|
fragmentFixture("text", "text-summary", ["## Goal\n- Recover raw overflow"]).completeEvents,
|
||||||
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
|
fragmentFixture("text", "text-final", ["Recovered"]).completeEvents,
|
||||||
]
|
]
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests).toHaveLength(3)
|
expect(requests).toHaveLength(3)
|
||||||
|
|
@ -1415,7 +1415,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
[LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" })],
|
[LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" })],
|
||||||
[LLMEvent.providerError({ message: "summary unavailable" })],
|
[LLMEvent.providerError({ message: "summary unavailable" })],
|
||||||
]
|
]
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
expect(requests).toHaveLength(2)
|
expect(requests).toHaveLength(2)
|
||||||
|
|
@ -1438,7 +1438,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const firstGate = yield* Deferred.make<void>()
|
const firstGate = yield* Deferred.make<void>()
|
||||||
const summaryGate = yield* Deferred.make<void>()
|
const summaryGate = yield* Deferred.make<void>()
|
||||||
streamGate = firstGate
|
streamGate = firstGate
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false })
|
||||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||||
while (requests.length < 1) yield* Effect.yieldNow
|
while (requests.length < 1) yield* Effect.yieldNow
|
||||||
streamGate = summaryGate
|
streamGate = summaryGate
|
||||||
|
|
@ -1458,13 +1458,13 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
systemBaseline = "Changed context"
|
systemBaseline = "Changed context"
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
const compactionID = SessionMessage.ID.create()
|
const compactionID = SessionMessage.ID.create()
|
||||||
yield* events.publish(SessionEvent.Compaction.Started, {
|
yield* events.publish(SessionEvent.Compaction.Started, {
|
||||||
|
|
@ -1478,7 +1478,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
recent: "",
|
recent: "",
|
||||||
})
|
})
|
||||||
systemUnavailable = true
|
systemUnavailable = true
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Third" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Third" }), resume: false })
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
// The rebaseline proceeds while the source is unobservable, restating the model's belief.
|
// The rebaseline proceeds while the source is unobservable, restating the model's belief.
|
||||||
|
|
@ -1491,7 +1491,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Use tools" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Use tools" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = undefined
|
responses = undefined
|
||||||
|
|
@ -1589,7 +1589,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo this" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Echo this" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
authorizations.length = 0
|
authorizations.length = 0
|
||||||
|
|
@ -1648,7 +1648,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo this" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Echo this" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -1690,7 +1690,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Think first" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Think first" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = [
|
response = [
|
||||||
|
|
@ -1728,7 +1728,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false })
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
|
|
@ -1747,7 +1747,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Search first" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Search first" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = [
|
response = [
|
||||||
|
|
@ -1772,7 +1772,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
yield* replaySessionProjection(sessionID)
|
yield* replaySessionProjection(sessionID)
|
||||||
|
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Continue" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Continue" }), resume: false })
|
||||||
response = []
|
response = []
|
||||||
yield* session.resume(sessionID)
|
yield* session.resume(sessionID)
|
||||||
|
|
||||||
|
|
@ -1802,7 +1802,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo five times" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Echo five times" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
executions.length = 0
|
executions.length = 0
|
||||||
|
|
@ -1863,7 +1863,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Echo twice" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Echo twice" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
executions.length = 0
|
executions.length = 0
|
||||||
|
|
@ -1951,7 +1951,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run once" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Run once" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = undefined
|
responses = undefined
|
||||||
|
|
@ -1990,7 +1990,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start working" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2010,7 +2010,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Change direction" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Change direction" }) })
|
||||||
yield* Deferred.succeed(streamGate, undefined)
|
yield* Deferred.succeed(streamGate, undefined)
|
||||||
yield* Fiber.join(first)
|
yield* Fiber.join(first)
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
|
|
@ -2033,7 +2033,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start working" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2061,7 +2061,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Wait until continuation ends" }),
|
prompt: PromptInput.Prompt.make({ text: "Wait until continuation ends" }),
|
||||||
delivery: "queue",
|
delivery: "queue",
|
||||||
})
|
})
|
||||||
yield* Deferred.succeed(streamGate, undefined)
|
yield* Deferred.succeed(streamGate, undefined)
|
||||||
|
|
@ -2081,7 +2081,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt current work" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt current work" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2099,7 +2099,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Run after interrupt" }),
|
prompt: PromptInput.Prompt.make({ text: "Run after interrupt" }),
|
||||||
delivery: "queue",
|
delivery: "queue",
|
||||||
})
|
})
|
||||||
yield* session.interrupt(sessionID)
|
yield* session.interrupt(sessionID)
|
||||||
|
|
@ -2124,7 +2124,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt current work" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt current work" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2142,7 +2142,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Steer after interrupt" }),
|
prompt: PromptInput.Prompt.make({ text: "Steer after interrupt" }),
|
||||||
})
|
})
|
||||||
yield* session.interrupt(sessionID)
|
yield* session.interrupt(sessionID)
|
||||||
expect(yield* Fiber.await(run)).toMatchObject({ _tag: "Failure" })
|
expect(yield* Fiber.await(run)).toMatchObject({ _tag: "Failure" })
|
||||||
|
|
@ -2166,7 +2166,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start working" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2191,8 +2191,8 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue first" }), delivery: "queue" })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue first" }), delivery: "queue" })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue second" }), delivery: "queue" })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue second" }), delivery: "queue" })
|
||||||
yield* Deferred.succeed(streamGate, undefined)
|
yield* Deferred.succeed(streamGate, undefined)
|
||||||
yield* Fiber.join(first)
|
yield* Fiber.join(first)
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
|
|
@ -2209,10 +2209,10 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start steering" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start steering" }), resume: false })
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Queue for later" }),
|
prompt: PromptInput.Prompt.make({ text: "Queue for later" }),
|
||||||
delivery: "queue",
|
delivery: "queue",
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
@ -2243,7 +2243,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start working" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2274,13 +2274,13 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||||
while (requests.length < 1) yield* Effect.yieldNow
|
while (requests.length < 1) yield* Effect.yieldNow
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue first" }), delivery: "queue" })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue first" }), delivery: "queue" })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Queue second" }), delivery: "queue" })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue second" }), delivery: "queue" })
|
||||||
streamGate = secondGate
|
streamGate = secondGate
|
||||||
yield* Deferred.succeed(firstGate, undefined)
|
yield* Deferred.succeed(firstGate, undefined)
|
||||||
while (requests.length < 2) yield* Effect.yieldNow
|
while (requests.length < 2) yield* Effect.yieldNow
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Steer before next queued input" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Steer before next queued input" }) })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Also steer before next queued input" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Also steer before next queued input" }) })
|
||||||
yield* Deferred.succeed(secondGate, undefined)
|
yield* Deferred.succeed(secondGate, undefined)
|
||||||
yield* Fiber.join(first)
|
yield* Fiber.join(first)
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
|
|
@ -2308,7 +2308,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start working" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2328,8 +2328,8 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "First steer" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First steer" }) })
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Second steer" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second steer" }) })
|
||||||
yield* Deferred.succeed(streamGate, undefined)
|
yield* Deferred.succeed(streamGate, undefined)
|
||||||
yield* Fiber.join(first)
|
yield* Fiber.join(first)
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
|
|
@ -2348,7 +2348,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start working" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start working" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = undefined
|
responses = undefined
|
||||||
|
|
@ -2359,7 +2359,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover with this" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Recover with this" }) })
|
||||||
yield* Deferred.succeed(streamGate, undefined)
|
yield* Deferred.succeed(streamGate, undefined)
|
||||||
expect(yield* Fiber.join(first).pipe(Effect.flip)).toBe(streamFailure)
|
expect(yield* Fiber.join(first).pipe(Effect.flip)).toBe(streamFailure)
|
||||||
|
|
||||||
|
|
@ -2378,7 +2378,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover interrupted tool" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Recover interrupted tool" }), resume: false })
|
||||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
||||||
const assistantMessageID = SessionMessage.ID.create()
|
const assistantMessageID = SessionMessage.ID.create()
|
||||||
yield* events.publish(SessionEvent.Step.Started, {
|
yield* events.publish(SessionEvent.Step.Started, {
|
||||||
|
|
@ -2436,7 +2436,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Recover interrupted hosted tool" }),
|
prompt: PromptInput.Prompt.make({ text: "Recover interrupted hosted tool" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
||||||
|
|
@ -2492,7 +2492,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const events = yield* EventV2.Service
|
const events = yield* EventV2.Service
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Recover interrupted tool input" }),
|
prompt: PromptInput.Prompt.make({ text: "Recover interrupted tool input" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
yield* SessionInput.promoteSteers((yield* Database.Service).db, events, sessionID)
|
||||||
|
|
@ -2528,7 +2528,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Wait in queue" }),
|
prompt: PromptInput.Prompt.make({ text: "Wait in queue" }),
|
||||||
delivery: "queue",
|
delivery: "queue",
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
@ -2550,7 +2550,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const defect = new Error("fail after prompt promotion")
|
const defect = new Error("fail after prompt promotion")
|
||||||
let fail = true
|
let fail = true
|
||||||
yield* events.project(SessionEvent.PromptPromoted, () => (fail ? Effect.die(defect) : Effect.void))
|
yield* events.project(SessionEvent.PromptPromoted, () => (fail ? Effect.die(defect) : Effect.void))
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Recover promoted input" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Recover promoted input" }), resume: false })
|
||||||
|
|
||||||
expect(yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed))).toBe(defect)
|
expect(yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed))).toBe(defect)
|
||||||
fail = false
|
fail = false
|
||||||
|
|
@ -2580,7 +2580,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
)
|
)
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Run committed promotion" }),
|
prompt: PromptInput.Prompt.make({ text: "Run committed promotion" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2597,8 +2597,8 @@ describe("SessionRunnerLLM", () => {
|
||||||
yield* setup
|
yield* setup
|
||||||
yield* insertSession(otherSessionID)
|
yield* insertSession(otherSessionID)
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Run first" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Run first" }), resume: false })
|
||||||
yield* session.prompt({ sessionID: otherSessionID, prompt: Prompt.make({ text: "Run second" }), resume: false })
|
yield* session.prompt({ sessionID: otherSessionID, prompt: PromptInput.Prompt.make({ text: "Run second" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = undefined
|
responses = undefined
|
||||||
|
|
@ -2635,12 +2635,12 @@ describe("SessionRunnerLLM", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID: longSessionID,
|
sessionID: longSessionID,
|
||||||
prompt: Prompt.make({ text: "Run long session" }),
|
prompt: PromptInput.Prompt.make({ text: "Run long session" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID: otherLongSessionID,
|
sessionID: otherLongSessionID,
|
||||||
prompt: Prompt.make({ text: "Run other long session" }),
|
prompt: PromptInput.Prompt.make({ text: "Run other long session" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -2659,7 +2659,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Retry after failure" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Retry after failure" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = undefined
|
responses = undefined
|
||||||
|
|
@ -2690,7 +2690,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call missing" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Call missing" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2736,7 +2736,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call defect" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Call defect" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2784,7 +2784,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call storefail" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Call storefail" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2836,7 +2836,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
execute: () => Effect.die(new QuestionTool.CancelledError()),
|
execute: () => Effect.die(new QuestionTool.CancelledError()),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Ask then stop" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Ask then stop" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = [
|
responses = [
|
||||||
|
|
@ -2875,7 +2875,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Settle before failing" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Settle before failing" }), resume: false })
|
||||||
const failure = providerUnavailable()
|
const failure = providerUnavailable()
|
||||||
toolExecutionGate = yield* Deferred.make<void>()
|
toolExecutionGate = yield* Deferred.make<void>()
|
||||||
responseStream = Stream.concat(
|
responseStream = Stream.concat(
|
||||||
|
|
@ -2909,7 +2909,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt blocked tool" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt blocked tool" }), resume: false })
|
||||||
executions.length = 0
|
executions.length = 0
|
||||||
toolExecutionGate = yield* Deferred.make<void>()
|
toolExecutionGate = yield* Deferred.make<void>()
|
||||||
responseStream = Stream.concat(
|
responseStream = Stream.concat(
|
||||||
|
|
@ -2959,7 +2959,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt provider" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt provider" }), resume: false })
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = []
|
response = []
|
||||||
streamGate = yield* Deferred.make<void>()
|
streamGate = yield* Deferred.make<void>()
|
||||||
|
|
@ -2987,7 +2987,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Interrupt tool settlement" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Interrupt tool settlement" }), resume: false })
|
||||||
executions.length = 0
|
executions.length = 0
|
||||||
toolExecutionGate = yield* Deferred.make<void>()
|
toolExecutionGate = yield* Deferred.make<void>()
|
||||||
toolExecutionsStarted = yield* Deferred.make<void>()
|
toolExecutionsStarted = yield* Deferred.make<void>()
|
||||||
|
|
@ -3037,7 +3037,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Finish at the limit" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Finish at the limit" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
executions.length = 0
|
executions.length = 0
|
||||||
|
|
@ -3085,7 +3085,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Start work" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Start work" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
executions.length = 0
|
executions.length = 0
|
||||||
|
|
@ -3113,7 +3113,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
|
|
||||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||||
yield* Deferred.await(streamStarted)
|
yield* Deferred.await(streamStarted)
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Change direction" }) })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Change direction" }) })
|
||||||
yield* Deferred.succeed(streamGate, undefined)
|
yield* Deferred.succeed(streamGate, undefined)
|
||||||
yield* Fiber.join(run)
|
yield* Fiber.join(run)
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
|
|
@ -3131,7 +3131,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail durably" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail durably" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
responses = undefined
|
responses = undefined
|
||||||
|
|
@ -3153,7 +3153,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail before step" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail before step" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = [LLMEvent.providerError({ message: "Provider unavailable" })]
|
response = [LLMEvent.providerError({ message: "Provider unavailable" })]
|
||||||
|
|
@ -3172,7 +3172,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail after output" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail after output" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = [
|
response = [
|
||||||
|
|
@ -3201,7 +3201,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail raw stream durably" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail raw stream durably" }), resume: false })
|
||||||
const failure = providerUnavailable()
|
const failure = providerUnavailable()
|
||||||
responseStream = Stream.fail(failure)
|
responseStream = Stream.fail(failure)
|
||||||
|
|
||||||
|
|
@ -3220,7 +3220,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Do not continue failed provider" }),
|
prompt: PromptInput.Prompt.make({ text: "Do not continue failed provider" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -3243,7 +3243,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail hosted tool durably" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail hosted tool durably" }), resume: false })
|
||||||
|
|
||||||
requests.length = 0
|
requests.length = 0
|
||||||
response = [
|
response = [
|
||||||
|
|
@ -3274,7 +3274,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Fail hosted tool at EOF" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail hosted tool at EOF" }), resume: false })
|
||||||
response = [
|
response = [
|
||||||
LLMEvent.stepStart({ index: 0 }),
|
LLMEvent.stepStart({ index: 0 }),
|
||||||
LLMEvent.toolCall({
|
LLMEvent.toolCall({
|
||||||
|
|
@ -3301,7 +3301,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({
|
yield* session.prompt({
|
||||||
sessionID,
|
sessionID,
|
||||||
prompt: Prompt.make({ text: "Fail hosted tool on raw failure" }),
|
prompt: PromptInput.Prompt.make({ text: "Fail hosted tool on raw failure" }),
|
||||||
resume: false,
|
resume: false,
|
||||||
})
|
})
|
||||||
const failure = providerUnavailable()
|
const failure = providerUnavailable()
|
||||||
|
|
@ -3336,7 +3336,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Two blocks" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Two blocks" }), resume: false })
|
||||||
|
|
||||||
responses = undefined
|
responses = undefined
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
|
|
@ -3400,7 +3400,7 @@ describe("SessionRunnerLLM", () => {
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* setup
|
yield* setup
|
||||||
const session = yield* SessionV2.Service
|
const session = yield* SessionV2.Service
|
||||||
yield* session.prompt({ sessionID, prompt: Prompt.make({ text: "Call provider tool" }), resume: false })
|
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Call provider tool" }), resume: false })
|
||||||
|
|
||||||
responses = undefined
|
responses = undefined
|
||||||
streamGate = undefined
|
streamGate = undefined
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||||
import { EventV2 } from "@opencode-ai/core/event"
|
import { EventV2 } from "@opencode-ai/core/event"
|
||||||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||||
import { Prompt } from "@opencode-ai/core/session/prompt"
|
import { Prompt } from "@opencode-ai/schema/prompt"
|
||||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||||
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
||||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import { SessionV2 } from "@opencode-ai/core/session"
|
||||||
import { Agent } from "@opencode-ai/schema/agent"
|
import { Agent } from "@opencode-ai/schema/agent"
|
||||||
import { Location } from "@opencode-ai/schema/location"
|
import { Location } from "@opencode-ai/schema/location"
|
||||||
import { Model } from "@opencode-ai/schema/model"
|
import { Model } from "@opencode-ai/schema/model"
|
||||||
import { AgentAttachment, FileAttachment, Prompt, Source } from "@opencode-ai/schema/prompt"
|
|
||||||
import { Provider } from "@opencode-ai/schema/provider"
|
import { Provider } from "@opencode-ai/schema/provider"
|
||||||
import { Project } from "@opencode-ai/schema/project"
|
import { Project } from "@opencode-ai/schema/project"
|
||||||
import { ProjectDirectories } from "@opencode-ai/schema/project-directories"
|
import { ProjectDirectories } from "@opencode-ai/schema/project-directories"
|
||||||
import { PermissionV1 } from "@opencode-ai/schema/permission-v1"
|
import { PermissionV1 } from "@opencode-ai/schema/permission-v1"
|
||||||
|
import { Prompt } from "@opencode-ai/schema/prompt"
|
||||||
import { Session } from "@opencode-ai/schema/session"
|
import { Session } from "@opencode-ai/schema/session"
|
||||||
import { SessionInput } from "@opencode-ai/schema/session-input"
|
import { SessionInput } from "@opencode-ai/schema/session-input"
|
||||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||||
|
|
@ -47,7 +47,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||||
coreSessionInput,
|
coreSessionInput,
|
||||||
coreSessionMessage,
|
coreSessionMessage,
|
||||||
coreSessionTodo,
|
coreSessionTodo,
|
||||||
corePrompt,
|
|
||||||
coreSkill,
|
coreSkill,
|
||||||
coreV2Schema,
|
coreV2Schema,
|
||||||
coreSchema,
|
coreSchema,
|
||||||
|
|
@ -69,7 +68,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||||
import("@opencode-ai/core/session/input"),
|
import("@opencode-ai/core/session/input"),
|
||||||
import("@opencode-ai/core/session/message"),
|
import("@opencode-ai/core/session/message"),
|
||||||
import("@opencode-ai/core/session/todo"),
|
import("@opencode-ai/core/session/todo"),
|
||||||
import("@opencode-ai/core/session/prompt"),
|
|
||||||
import("@opencode-ai/core/skill"),
|
import("@opencode-ai/core/skill"),
|
||||||
import("@opencode-ai/core/v2-schema"),
|
import("@opencode-ai/core/v2-schema"),
|
||||||
import("@opencode-ai/core/schema"),
|
import("@opencode-ai/core/schema"),
|
||||||
|
|
@ -164,10 +162,6 @@ test("Core reuses the canonical shared schemas", async () => {
|
||||||
[coreSessionMessage.Message, SessionMessage.Message],
|
[coreSessionMessage.Message, SessionMessage.Message],
|
||||||
[coreSessionTodo.Info, SessionTodo.Info],
|
[coreSessionTodo.Info, SessionTodo.Info],
|
||||||
[coreSessionTodo.Event, SessionTodo.Event],
|
[coreSessionTodo.Event, SessionTodo.Event],
|
||||||
[corePrompt.Source, Source],
|
|
||||||
[corePrompt.FileAttachment, FileAttachment],
|
|
||||||
[corePrompt.AgentAttachment, AgentAttachment],
|
|
||||||
[corePrompt.Prompt, Prompt],
|
|
||||||
[coreSkill.DirectorySource, Skill.DirectorySource],
|
[coreSkill.DirectorySource, Skill.DirectorySource],
|
||||||
[coreSkill.UrlSource, Skill.UrlSource],
|
[coreSkill.UrlSource, Skill.UrlSource],
|
||||||
[coreSkill.EmbeddedSource, Skill.EmbeddedSource],
|
[coreSkill.EmbeddedSource, Skill.EmbeddedSource],
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,7 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||||
resume: Schema.Boolean.pipe(Schema.optional),
|
resume: Schema.Boolean.pipe(Schema.optional),
|
||||||
}),
|
}),
|
||||||
success: Schema.Struct({ data: SessionInput.Admitted }),
|
success: Schema.Struct({ data: SessionInput.Admitted }),
|
||||||
error: [ConflictError, SessionNotFoundError],
|
error: [ConflictError, InvalidRequestError, SessionNotFoundError],
|
||||||
})
|
})
|
||||||
.middleware(sessionLocationMiddleware)
|
.middleware(sessionLocationMiddleware)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
|
|
@ -290,7 +290,13 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||||
resume: Schema.Boolean.pipe(Schema.optional),
|
resume: Schema.Boolean.pipe(Schema.optional),
|
||||||
}),
|
}),
|
||||||
success: Schema.Struct({ data: SessionInput.Admitted }),
|
success: Schema.Struct({ data: SessionInput.Admitted }),
|
||||||
error: [ConflictError, SessionNotFoundError, CommandNotFoundError, CommandEvaluationError],
|
error: [
|
||||||
|
ConflictError,
|
||||||
|
InvalidRequestError,
|
||||||
|
SessionNotFoundError,
|
||||||
|
CommandNotFoundError,
|
||||||
|
CommandEvaluationError,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
.middleware(sessionLocationMiddleware)
|
.middleware(sessionLocationMiddleware)
|
||||||
.annotateMerge(
|
.annotateMerge(
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,6 @@ export { Pty } from "./pty.js"
|
||||||
export { PtyTicket } from "./pty-ticket.js"
|
export { PtyTicket } from "./pty-ticket.js"
|
||||||
export { Question } from "./question.js"
|
export { Question } from "./question.js"
|
||||||
export { Workspace } from "./workspace.js"
|
export { Workspace } from "./workspace.js"
|
||||||
export { Prompt, Source, FileAttachment, AgentAttachment } from "./prompt.js"
|
export { Prompt, PromptMention, FileSource, FileAttachment, AgentAttachment } from "./prompt.js"
|
||||||
export { PromptInput } from "./prompt-input.js"
|
export { PromptInput } from "./prompt-input.js"
|
||||||
export * from "./schema.js"
|
export * from "./schema.js"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export * as PromptInput from "./prompt-input.js"
|
export * as PromptInput from "./prompt-input.js"
|
||||||
|
|
||||||
import { Schema } from "effect"
|
import { Schema } from "effect"
|
||||||
import { AgentAttachment, Source } from "./prompt.js"
|
import { AgentAttachment, PromptMention } from "./prompt.js"
|
||||||
import { optional, statics } from "./schema.js"
|
import { optional, statics } from "./schema.js"
|
||||||
|
|
||||||
export interface FileAttachment extends Schema.Schema.Type<typeof FileAttachment> {}
|
export interface FileAttachment extends Schema.Schema.Type<typeof FileAttachment> {}
|
||||||
|
|
@ -9,7 +9,7 @@ export const FileAttachment = Schema.Struct({
|
||||||
uri: Schema.String,
|
uri: Schema.String,
|
||||||
name: Schema.String.pipe(optional),
|
name: Schema.String.pipe(optional),
|
||||||
description: Schema.String.pipe(optional),
|
description: Schema.String.pipe(optional),
|
||||||
source: Source.pipe(optional),
|
mention: PromptMention.pipe(optional),
|
||||||
})
|
})
|
||||||
.annotate({ identifier: "PromptInput.FileAttachment" })
|
.annotate({ identifier: "PromptInput.FileAttachment" })
|
||||||
.pipe(
|
.pipe(
|
||||||
|
|
|
||||||
|
|
@ -2,33 +2,46 @@ import { Schema } from "effect"
|
||||||
import { optional } from "./schema.js"
|
import { optional } from "./schema.js"
|
||||||
import { statics } from "./schema.js"
|
import { statics } from "./schema.js"
|
||||||
|
|
||||||
export interface Source extends Schema.Schema.Type<typeof Source> {}
|
export interface PromptMention extends Schema.Schema.Type<typeof PromptMention> {}
|
||||||
export const Source = Schema.Struct({
|
export const PromptMention = Schema.Struct({
|
||||||
start: Schema.Finite,
|
start: Schema.Finite,
|
||||||
end: Schema.Finite,
|
end: Schema.Finite,
|
||||||
text: Schema.String,
|
text: Schema.String,
|
||||||
}).annotate({ identifier: "Prompt.Source" })
|
}).annotate({ identifier: "Prompt.Mention" })
|
||||||
|
|
||||||
|
export const FileSource = Schema.Union([
|
||||||
|
Schema.Struct({ type: Schema.Literal("inline") }),
|
||||||
|
Schema.Struct({ type: Schema.Literal("uri"), uri: Schema.String }),
|
||||||
|
])
|
||||||
|
.pipe(Schema.toTaggedUnion("type"))
|
||||||
|
.annotate({ identifier: "Prompt.FileSource" })
|
||||||
|
export type FileSource = typeof FileSource.Type
|
||||||
|
|
||||||
|
export const Base64 = Schema.String.check(
|
||||||
|
Schema.isPattern(/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/),
|
||||||
|
).annotate({ identifier: "Prompt.Base64" })
|
||||||
|
export type Base64 = typeof Base64.Type
|
||||||
|
|
||||||
export interface FileAttachment extends Schema.Schema.Type<typeof FileAttachment> {}
|
export interface FileAttachment extends Schema.Schema.Type<typeof FileAttachment> {}
|
||||||
export const FileAttachment = Schema.Struct({
|
export const FileAttachment = Schema.Struct({
|
||||||
uri: Schema.String,
|
data: Base64,
|
||||||
mime: Schema.String,
|
mime: Schema.String,
|
||||||
content: Schema.String.pipe(optional),
|
source: FileSource,
|
||||||
name: Schema.String.pipe(optional),
|
name: Schema.String.pipe(optional),
|
||||||
description: Schema.String.pipe(optional),
|
description: Schema.String.pipe(optional),
|
||||||
source: Source.pipe(optional),
|
mention: PromptMention.pipe(optional),
|
||||||
})
|
})
|
||||||
.annotate({ identifier: "Prompt.FileAttachment" })
|
.annotate({ identifier: "Prompt.FileAttachment" })
|
||||||
.pipe(
|
.pipe(
|
||||||
statics((schema) => ({
|
statics((schema) => ({
|
||||||
create: (input: FileAttachment) =>
|
create: (input: FileAttachment) =>
|
||||||
schema.make({
|
schema.make({
|
||||||
uri: input.uri,
|
data: input.data,
|
||||||
mime: input.mime,
|
mime: input.mime,
|
||||||
content: input.content,
|
source: input.source,
|
||||||
name: input.name,
|
name: input.name,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
source: input.source,
|
mention: input.mention,
|
||||||
}),
|
}),
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|
@ -36,7 +49,7 @@ export const FileAttachment = Schema.Struct({
|
||||||
export interface AgentAttachment extends Schema.Schema.Type<typeof AgentAttachment> {}
|
export interface AgentAttachment extends Schema.Schema.Type<typeof AgentAttachment> {}
|
||||||
export const AgentAttachment = Schema.Struct({
|
export const AgentAttachment = Schema.Struct({
|
||||||
name: Schema.String,
|
name: Schema.String,
|
||||||
source: Source.pipe(optional),
|
mention: PromptMention.pipe(optional),
|
||||||
}).annotate({ identifier: "Prompt.AgentAttachment" })
|
}).annotate({ identifier: "Prompt.AgentAttachment" })
|
||||||
|
|
||||||
export interface Prompt extends Schema.Schema.Type<typeof Prompt> {}
|
export interface Prompt extends Schema.Schema.Type<typeof Prompt> {}
|
||||||
|
|
|
||||||
|
|
@ -3255,24 +3255,35 @@ export type LocationRef = {
|
||||||
workspaceID?: string
|
workspaceID?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptSource = {
|
export type PromptBase64 = string
|
||||||
|
|
||||||
|
export type PromptFileSource =
|
||||||
|
| {
|
||||||
|
type: "inline"
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "uri"
|
||||||
|
uri: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PromptMention = {
|
||||||
start: number
|
start: number
|
||||||
end: number
|
end: number
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptFileAttachment = {
|
export type PromptFileAttachment = {
|
||||||
uri: string
|
data: PromptBase64
|
||||||
mime: string
|
mime: string
|
||||||
content?: string
|
source: PromptFileSource
|
||||||
name?: string
|
name?: string
|
||||||
description?: string
|
description?: string
|
||||||
source?: PromptSource
|
mention?: PromptMention
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptAgentAttachment = {
|
export type PromptAgentAttachment = {
|
||||||
name: string
|
name: string
|
||||||
source?: PromptSource
|
mention?: PromptMention
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionErrorUnknown = {
|
export type SessionErrorUnknown = {
|
||||||
|
|
@ -4279,7 +4290,7 @@ export type PromptInputFileAttachment = {
|
||||||
uri: string
|
uri: string
|
||||||
name?: string
|
name?: string
|
||||||
description?: string
|
description?: string
|
||||||
source?: PromptSource
|
mention?: PromptMention
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SessionInputAdmitted = {
|
export type SessionInputAdmitted = {
|
||||||
|
|
@ -7877,7 +7888,7 @@ export type MessageNotFoundErrorV2 = {
|
||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptSource2 = {
|
export type PromptMention2 = {
|
||||||
start: number
|
start: number
|
||||||
end: number
|
end: number
|
||||||
text: string
|
text: string
|
||||||
|
|
@ -7887,12 +7898,12 @@ export type PromptInputFileAttachment2 = {
|
||||||
uri: string
|
uri: string
|
||||||
name?: string
|
name?: string
|
||||||
description?: string
|
description?: string
|
||||||
source?: PromptSource2
|
mention?: PromptMention2
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptAgentAttachment2 = {
|
export type PromptAgentAttachment2 = {
|
||||||
name: string
|
name: string
|
||||||
source?: PromptSource2
|
mention?: PromptMention2
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptInputV2 = {
|
export type PromptInputV2 = {
|
||||||
|
|
@ -7901,13 +7912,24 @@ export type PromptInputV2 = {
|
||||||
agents?: Array<PromptAgentAttachment2>
|
agents?: Array<PromptAgentAttachment2>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type PromptBase642 = string
|
||||||
|
|
||||||
|
export type PromptFileSource2 =
|
||||||
|
| {
|
||||||
|
type: "inline"
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "uri"
|
||||||
|
uri: string
|
||||||
|
}
|
||||||
|
|
||||||
export type PromptFileAttachment2 = {
|
export type PromptFileAttachment2 = {
|
||||||
uri: string
|
data: PromptBase642
|
||||||
mime: string
|
mime: string
|
||||||
content?: string
|
source: PromptFileSource2
|
||||||
name?: string
|
name?: string
|
||||||
description?: string
|
description?: string
|
||||||
source?: PromptSource2
|
mention?: PromptMention2
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PromptV2 = {
|
export type PromptV2 = {
|
||||||
|
|
@ -15965,7 +15987,7 @@ export type V2SessionPromptErrors = {
|
||||||
/**
|
/**
|
||||||
* InvalidRequestError
|
* InvalidRequestError
|
||||||
*/
|
*/
|
||||||
400: InvalidRequestErrorV2
|
400: InvalidRequestError1 | InvalidRequestErrorV2
|
||||||
/**
|
/**
|
||||||
* UnauthorizedError
|
* UnauthorizedError
|
||||||
*/
|
*/
|
||||||
|
|
@ -16016,7 +16038,7 @@ export type V2SessionCommandErrors = {
|
||||||
/**
|
/**
|
||||||
* InvalidRequestError
|
* InvalidRequestError
|
||||||
*/
|
*/
|
||||||
400: InvalidRequestErrorV2
|
400: InvalidRequestError1 | InvalidRequestErrorV2
|
||||||
/**
|
/**
|
||||||
* UnauthorizedError
|
* UnauthorizedError
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
CommandEvaluationError,
|
CommandEvaluationError,
|
||||||
CommandNotFoundError,
|
CommandNotFoundError,
|
||||||
InvalidCursorError,
|
InvalidCursorError,
|
||||||
|
InvalidRequestError,
|
||||||
MessageNotFoundError,
|
MessageNotFoundError,
|
||||||
ServiceUnavailableError,
|
ServiceUnavailableError,
|
||||||
SessionBusyError,
|
SessionBusyError,
|
||||||
|
|
@ -213,6 +214,9 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Effect.catchTag("Session.AttachmentError", (error) =>
|
||||||
|
Effect.fail(new InvalidRequestError({ message: error.message, field: "prompt.files" })),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
@ -267,6 +271,9 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Effect.catchTag("Session.AttachmentError", (error) =>
|
||||||
|
Effect.fail(new InvalidRequestError({ message: error.message, field: "files" })),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -210,17 +210,17 @@ export function Autocomplete(props: {
|
||||||
const existingIndex = files.findIndex((file) => file.uri === part.value.uri)
|
const existingIndex = files.findIndex((file) => file.uri === part.value.uri)
|
||||||
if (existingIndex !== -1) {
|
if (existingIndex !== -1) {
|
||||||
const existing = files[existingIndex]
|
const existing = files[existingIndex]
|
||||||
if (existing?.source) {
|
if (existing?.mention) {
|
||||||
existing.source.start = extmarkStart
|
existing.mention.start = extmarkStart
|
||||||
existing.source.end = extmarkEnd
|
existing.mention.end = extmarkEnd
|
||||||
existing.source.text = virtualText
|
existing.mention.text = virtualText
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (part.value.source) {
|
if (part.value.mention) {
|
||||||
part.value.source.start = extmarkStart
|
part.value.mention.start = extmarkStart
|
||||||
part.value.source.end = extmarkEnd
|
part.value.mention.end = extmarkEnd
|
||||||
part.value.source.text = virtualText
|
part.value.mention.text = virtualText
|
||||||
}
|
}
|
||||||
const index = files.length
|
const index = files.length
|
||||||
files.push(part.value)
|
files.push(part.value)
|
||||||
|
|
@ -229,10 +229,10 @@ export function Autocomplete(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const agents = (draft.agents ??= [])
|
const agents = (draft.agents ??= [])
|
||||||
if (part.value.source) {
|
if (part.value.mention) {
|
||||||
part.value.source.start = extmarkStart
|
part.value.mention.start = extmarkStart
|
||||||
part.value.source.end = extmarkEnd
|
part.value.mention.end = extmarkEnd
|
||||||
part.value.source.text = virtualText
|
part.value.mention.text = virtualText
|
||||||
}
|
}
|
||||||
const index = agents.length
|
const index = agents.length
|
||||||
agents.push(part.value)
|
agents.push(part.value)
|
||||||
|
|
@ -268,7 +268,7 @@ export function Autocomplete(props: {
|
||||||
value: {
|
value: {
|
||||||
uri: urlObj.href,
|
uri: urlObj.href,
|
||||||
name: filename,
|
name: filename,
|
||||||
source: { start: 0, end: 0, text: "" },
|
mention: { start: 0, end: 0, text: "" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -376,7 +376,7 @@ export function Autocomplete(props: {
|
||||||
uri: res.uri,
|
uri: res.uri,
|
||||||
name: res.name,
|
name: res.name,
|
||||||
description: res.description,
|
description: res.description,
|
||||||
source: { start: 0, end: 0, text: "" },
|
mention: { start: 0, end: 0, text: "" },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -397,7 +397,7 @@ export function Autocomplete(props: {
|
||||||
type: "agent",
|
type: "agent",
|
||||||
value: {
|
value: {
|
||||||
name: agent.id,
|
name: agent.id,
|
||||||
source: { start: 0, end: 0, text: "" },
|
mention: { start: 0, end: 0, text: "" },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -419,7 +419,7 @@ export function Autocomplete(props: {
|
||||||
value: {
|
value: {
|
||||||
uri: pathToFileURL(reference.path).href,
|
uri: pathToFileURL(reference.path).href,
|
||||||
name: reference.name,
|
name: reference.name,
|
||||||
source: { start: 0, end: 0, text: "" },
|
mention: { start: 0, end: 0, text: "" },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -483,17 +483,17 @@ export function Prompt(props: PromptProps) {
|
||||||
// Update attachment positions and drop virtual text deleted in the editor.
|
// Update attachment positions and drop virtual text deleted in the editor.
|
||||||
// this handles a case where the user edits the text in the editor
|
// this handles a case where the user edits the text in the editor
|
||||||
// such that the virtual text moves around or is deleted
|
// such that the virtual text moves around or is deleted
|
||||||
const moveSource = <Part extends { source?: { start: number; end: number; text: string } }>(part: Part) => {
|
const moveMention = <Part extends { mention?: { start: number; end: number; text: string } }>(part: Part) => {
|
||||||
if (!part.source?.text) return part
|
if (!part.mention?.text) return part
|
||||||
const start = normalized.indexOf(part.source.text)
|
const start = normalized.indexOf(part.mention.text)
|
||||||
if (start === -1) return
|
if (start === -1) return
|
||||||
return { ...part, source: { ...part.source, start, end: start + part.source.text.length } }
|
return { ...part, mention: { ...part.mention, start, end: start + part.mention.text.length } }
|
||||||
}
|
}
|
||||||
|
|
||||||
setStore("prompt", {
|
setStore("prompt", {
|
||||||
text: normalized,
|
text: normalized,
|
||||||
files: store.prompt.files?.map(moveSource).filter((part) => part !== undefined),
|
files: store.prompt.files?.map(moveMention).filter((part) => part !== undefined),
|
||||||
agents: store.prompt.agents?.map(moveSource).filter((part) => part !== undefined),
|
agents: store.prompt.agents?.map(moveMention).filter((part) => part !== undefined),
|
||||||
pasted: [],
|
pasted: [],
|
||||||
})
|
})
|
||||||
restoreExtmarksFromPrompt(store.prompt)
|
restoreExtmarksFromPrompt(store.prompt)
|
||||||
|
|
@ -647,16 +647,28 @@ export function Prompt(props: PromptProps) {
|
||||||
setStore("extmarkToPart", new Map())
|
setStore("extmarkToPart", new Map())
|
||||||
|
|
||||||
const parts = [
|
const parts = [
|
||||||
...(prompt.files ?? []).map((part, index) => ({ part, ref: { type: "file" as const, index }, styleId: fileStyleId })),
|
...(prompt.files ?? []).map((part, index) => ({
|
||||||
...(prompt.agents ?? []).map((part, index) => ({ part, ref: { type: "agent" as const, index }, styleId: agentStyleId })),
|
mention: part.mention,
|
||||||
...prompt.pasted.map((part, index) => ({ part, ref: { type: "pasted" as const, index }, styleId: pasteStyleId })),
|
ref: { type: "file" as const, index },
|
||||||
|
styleId: fileStyleId,
|
||||||
|
})),
|
||||||
|
...(prompt.agents ?? []).map((part, index) => ({
|
||||||
|
mention: part.mention,
|
||||||
|
ref: { type: "agent" as const, index },
|
||||||
|
styleId: agentStyleId,
|
||||||
|
})),
|
||||||
|
...prompt.pasted.map((part, index) => ({
|
||||||
|
mention: part.source,
|
||||||
|
ref: { type: "pasted" as const, index },
|
||||||
|
styleId: pasteStyleId,
|
||||||
|
})),
|
||||||
]
|
]
|
||||||
|
|
||||||
parts.forEach(({ part, ref, styleId }) => {
|
parts.forEach(({ mention, ref, styleId }) => {
|
||||||
if (part.source?.text) {
|
if (mention?.text) {
|
||||||
const extmarkId = input.extmarks.create({
|
const extmarkId = input.extmarks.create({
|
||||||
start: part.source.start,
|
start: mention.start,
|
||||||
end: part.source.end,
|
end: mention.end,
|
||||||
virtual: true,
|
virtual: true,
|
||||||
styleId,
|
styleId,
|
||||||
typeId: promptPartTypeId,
|
typeId: promptPartTypeId,
|
||||||
|
|
@ -684,9 +696,9 @@ export function Prompt(props: PromptProps) {
|
||||||
if (!ref) continue
|
if (!ref) continue
|
||||||
if (ref.type === "file") {
|
if (ref.type === "file") {
|
||||||
const part = draft.prompt.files?.[ref.index]
|
const part = draft.prompt.files?.[ref.index]
|
||||||
if (!part?.source) continue
|
if (!part?.mention) continue
|
||||||
part.source.start = extmark.start
|
part.mention.start = extmark.start
|
||||||
part.source.end = extmark.end
|
part.mention.end = extmark.end
|
||||||
const index = files.length
|
const index = files.length
|
||||||
files.push(part)
|
files.push(part)
|
||||||
newMap.set(extmark.id, { type: "file", index })
|
newMap.set(extmark.id, { type: "file", index })
|
||||||
|
|
@ -694,9 +706,9 @@ export function Prompt(props: PromptProps) {
|
||||||
}
|
}
|
||||||
if (ref.type === "agent") {
|
if (ref.type === "agent") {
|
||||||
const part = draft.prompt.agents?.[ref.index]
|
const part = draft.prompt.agents?.[ref.index]
|
||||||
if (!part?.source) continue
|
if (!part?.mention) continue
|
||||||
part.source.start = extmark.start
|
part.mention.start = extmark.start
|
||||||
part.source.end = extmark.end
|
part.mention.end = extmark.end
|
||||||
const index = agents.length
|
const index = agents.length
|
||||||
agents.push(part)
|
agents.push(part)
|
||||||
newMap.set(extmark.id, { type: "agent", index })
|
newMap.set(extmark.id, { type: "agent", index })
|
||||||
|
|
@ -1248,7 +1260,7 @@ export function Prompt(props: PromptProps) {
|
||||||
const part: NonNullable<PromptInfo["files"]>[number] = {
|
const part: NonNullable<PromptInfo["files"]>[number] = {
|
||||||
uri: file.uri,
|
uri: file.uri,
|
||||||
name: file.filename,
|
name: file.filename,
|
||||||
source: {
|
mention: {
|
||||||
start: extmarkStart,
|
start: extmarkStart,
|
||||||
end: extmarkEnd,
|
end: extmarkEnd,
|
||||||
text: virtualText,
|
text: virtualText,
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ export function DialogForkFromTimeline(props: { sessionID: string; onMove: (mess
|
||||||
files.push({
|
files.push({
|
||||||
uri: part.url,
|
uri: part.url,
|
||||||
name: part.filename,
|
name: part.filename,
|
||||||
source: part.source?.text
|
mention: part.source?.text
|
||||||
? {
|
? {
|
||||||
start: part.source.text.start,
|
start: part.source.text.start,
|
||||||
end: part.source.text.end,
|
end: part.source.text.end,
|
||||||
|
|
|
||||||
|
|
@ -1428,7 +1428,7 @@ function UserMessage(props: { message: SessionMessageUser }) {
|
||||||
</span>
|
</span>
|
||||||
<span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}>
|
<span style={{ bg: theme.backgroundElement, fg: theme.textMuted }}>
|
||||||
{" "}
|
{" "}
|
||||||
{file.name ?? file.uri}{" "}
|
{file.name ?? (file.source.type === "uri" ? file.source.uri : "attachment")}{" "}
|
||||||
</span>
|
</span>
|
||||||
</text>
|
</text>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue