chore(observability): merge v2
This commit is contained in:
commit
f4e661f874
276 changed files with 17001 additions and 3755 deletions
|
|
@ -65,7 +65,6 @@ import { Snapshot } from "@opencode-ai/core/snapshot"
|
|||
import { SessionEvent } from "@opencode-ai/core/session/event"
|
||||
import { SessionInput } from "@opencode-ai/core/session/input"
|
||||
import { SessionMessage } from "@opencode-ai/core/session/message"
|
||||
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
|
|
@ -109,6 +108,7 @@ const requests: LLMRequest[] = []
|
|||
let response: LLMEvent[] = []
|
||||
let responses: LLMEvent[][] | undefined
|
||||
let responseStream: Stream.Stream<LLMEvent, LLMError> | undefined
|
||||
let responseStreams: Stream.Stream<LLMEvent, LLMError>[] | undefined
|
||||
let streamGate: Deferred.Deferred<void> | undefined
|
||||
let streamStarted: Deferred.Deferred<void> | undefined
|
||||
let streamFailure: LLMError | undefined
|
||||
|
|
@ -123,6 +123,7 @@ const client = Layer.succeed(
|
|||
prepare: () => Effect.die("unused"),
|
||||
stream: ((request: LLMRequest) => {
|
||||
requests.push(request)
|
||||
if (responseStreams) return responseStreams.shift() ?? Stream.empty
|
||||
if (responseStream) {
|
||||
const stream = responseStream
|
||||
responseStream = undefined
|
||||
|
|
@ -444,8 +445,7 @@ const it = testEffect(
|
|||
)
|
||||
const sessionID = SessionV2.ID.make("ses_runner_test")
|
||||
const otherSessionID = SessionV2.ID.make("ses_runner_other")
|
||||
const admit = (session: SessionV2.Interface, text: string) =>
|
||||
session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text }), resume: false })
|
||||
const admit = (session: SessionV2.Interface, text: string) => session.prompt({ sessionID, text, resume: false })
|
||||
|
||||
const insertSession = (id: SessionV2.ID) =>
|
||||
Effect.gen(function* () {
|
||||
|
|
@ -482,6 +482,7 @@ const setup = Effect.gen(function* () {
|
|||
responses = undefined
|
||||
streamFailure = undefined
|
||||
responseStream = undefined
|
||||
responseStreams = undefined
|
||||
streamGate = undefined
|
||||
streamStarted = undefined
|
||||
toolExecutionGate = undefined
|
||||
|
|
@ -781,7 +782,7 @@ describe("SessionRunnerLLM", () => {
|
|||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Use echo" }), resume: false })
|
||||
yield* session.prompt({ sessionID, text: "Use echo", resume: false })
|
||||
const usage = new Usage({
|
||||
inputTokens: 8,
|
||||
outputTokens: 3,
|
||||
|
|
@ -856,7 +857,7 @@ describe("SessionRunnerLLM", () => {
|
|||
})
|
||||
yield* session.prompt({
|
||||
sessionID: child.id,
|
||||
prompt: PromptInput.Prompt.make({ text: "Explore" }),
|
||||
text: "Explore",
|
||||
resume: false,
|
||||
})
|
||||
|
||||
|
|
@ -898,7 +899,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Use echo twice" }),
|
||||
text: "Use echo twice",
|
||||
resume: false,
|
||||
})
|
||||
responses = [
|
||||
|
|
@ -933,7 +934,7 @@ describe("SessionRunnerLLM", () => {
|
|||
Effect.gen(function* () {
|
||||
yield* setup
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Fail tool" }), resume: false })
|
||||
yield* session.prompt({ sessionID, text: "Fail tool", resume: false })
|
||||
responses = [
|
||||
[
|
||||
LLMEvent.stepStart({ index: 0 }),
|
||||
|
|
@ -1010,7 +1011,7 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const message = yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Run automatically" }),
|
||||
text: "Run automatically",
|
||||
})
|
||||
yield* session.wait(sessionID)
|
||||
|
||||
|
|
@ -1021,6 +1022,34 @@ describe("SessionRunnerLLM", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("runs a follow-up when synthetic input arrives during an active continuation", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
const secondStarted = yield* Deferred.make<void>()
|
||||
const releaseSecond = yield* Deferred.make<void>()
|
||||
responseStreams = [
|
||||
Stream.fromIterable(reply.tool("call-echo", "echo", { text: "background started" })),
|
||||
Stream.unwrap(
|
||||
Deferred.succeed(secondStarted, undefined).pipe(
|
||||
Effect.andThen(Deferred.await(releaseSecond)),
|
||||
Effect.as(Stream.fromIterable(reply.stop())),
|
||||
),
|
||||
),
|
||||
Stream.fromIterable(reply.text("Handled completion", "text-completion")),
|
||||
]
|
||||
yield* admit(session, "Start background work")
|
||||
const running = yield* session.resume(sessionID).pipe(Effect.forkChild({ startImmediately: true }))
|
||||
yield* Deferred.await(secondStarted)
|
||||
|
||||
yield* session.synthetic({ sessionID, text: "Background work completed" })
|
||||
yield* Deferred.succeed(releaseSecond, undefined)
|
||||
yield* Fiber.join(running)
|
||||
|
||||
expect(requests).toHaveLength(3)
|
||||
expect(userTexts(requests[2]!)).toContain("Background work completed")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("streams one request with registry definitions from chronological V2 user history", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* setup
|
||||
|
|
@ -1049,7 +1078,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* session.prompt({
|
||||
id: messageID,
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "First" }),
|
||||
text: "First",
|
||||
resume: false,
|
||||
})
|
||||
|
||||
|
|
@ -1068,7 +1097,7 @@ describe("SessionRunnerLLM", () => {
|
|||
).toBeUndefined()
|
||||
|
||||
systemUnavailable = false
|
||||
yield* session.prompt({ id: messageID, sessionID, prompt: PromptInput.Prompt.make({ text: "First" }) })
|
||||
yield* session.prompt({ id: messageID, sessionID, text: "First" })
|
||||
yield* session.wait(sessionID)
|
||||
|
||||
expect(requests).toHaveLength(1)
|
||||
|
|
@ -1335,7 +1364,7 @@ describe("SessionRunnerLLM", () => {
|
|||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Inspect files" }), resume: false })
|
||||
yield* session.prompt({ sessionID, text: "Inspect files", resume: false })
|
||||
|
||||
requests.length = 0
|
||||
response = []
|
||||
|
|
@ -1356,7 +1385,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const release = yield* Deferred.make<void>()
|
||||
pluginFlushHook = Deferred.await(release)
|
||||
const session = yield* SessionV2.Service
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Wait for plugins" }), resume: false })
|
||||
yield* session.prompt({ sessionID, text: "Wait for plugins", resume: false })
|
||||
|
||||
requests.length = 0
|
||||
response = []
|
||||
|
|
@ -1645,9 +1674,10 @@ describe("SessionRunnerLLM", () => {
|
|||
expect((yield* session.messages({ sessionID })).find((message) => message.id === first.id)).toBeUndefined()
|
||||
|
||||
yield* admit(session, "Steer after compaction")
|
||||
yield* session.synthetic({ sessionID, text: "Completion after compaction", resume: false })
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Queue after compaction" }),
|
||||
text: "Queue after compaction",
|
||||
delivery: "queue",
|
||||
resume: false,
|
||||
})
|
||||
|
|
@ -1659,6 +1689,7 @@ describe("SessionRunnerLLM", () => {
|
|||
expect(requests).toHaveLength(4)
|
||||
expect(userTexts(requests[1])[0]).toContain("Create a new anchored summary")
|
||||
expect(userTexts(requests[2])).toContain("Steer after compaction")
|
||||
expect(userTexts(requests[2])).toContain("Completion after compaction")
|
||||
expect(userTexts(requests[3])).toContain("Queue after compaction")
|
||||
expect(yield* SessionInput.pendingCompaction((yield* Database.Service).db, sessionID)).toBeUndefined()
|
||||
expect((yield* session.messages({ sessionID })).find((message) => message.id === first.id)).toMatchObject({
|
||||
|
|
@ -1691,7 +1722,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const compaction = yield* session.compact({ sessionID })
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Continue after failure" }),
|
||||
text: "Continue after failure",
|
||||
delivery: "queue",
|
||||
resume: false,
|
||||
})
|
||||
|
|
@ -2447,7 +2478,7 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Change direction" }) })
|
||||
yield* session.prompt({ sessionID, text: "Change direction" })
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
yield* Fiber.join(first)
|
||||
streamGate = undefined
|
||||
|
|
@ -2480,7 +2511,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Wait until continuation ends" }),
|
||||
text: "Wait until continuation ends",
|
||||
delivery: "queue",
|
||||
})
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
|
|
@ -2512,7 +2543,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Run after interrupt" }),
|
||||
text: "Run after interrupt",
|
||||
delivery: "queue",
|
||||
})
|
||||
yield* session.interrupt(sessionID)
|
||||
|
|
@ -2546,7 +2577,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Steer after interrupt" }),
|
||||
text: "Steer after interrupt",
|
||||
})
|
||||
yield* session.interrupt(sessionID)
|
||||
expect(yield* Fiber.await(run)).toMatchObject({ _tag: "Failure" })
|
||||
|
|
@ -2577,8 +2608,8 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue first" }), delivery: "queue" })
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue second" }), delivery: "queue" })
|
||||
yield* session.prompt({ sessionID, text: "Queue first", delivery: "queue" })
|
||||
yield* session.prompt({ sessionID, text: "Queue second", delivery: "queue" })
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
yield* Fiber.join(first)
|
||||
streamGate = undefined
|
||||
|
|
@ -2602,7 +2633,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* admit(session, "Start steering")
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Queue for later" }),
|
||||
text: "Queue for later",
|
||||
delivery: "queue",
|
||||
resume: false,
|
||||
})
|
||||
|
|
@ -2629,16 +2660,17 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
while (requests.length < 1) yield* Effect.yieldNow
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue first" }), delivery: "queue" })
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Queue second" }), delivery: "queue" })
|
||||
yield* session.prompt({ sessionID, text: "Queue first", delivery: "queue" })
|
||||
yield* session.prompt({ sessionID, text: "Queue second", delivery: "queue" })
|
||||
streamGate = secondGate
|
||||
yield* Deferred.succeed(firstGate, undefined)
|
||||
while (requests.length < 2) yield* Effect.yieldNow
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Steer before next queued input" }) })
|
||||
yield* session.prompt({ sessionID, text: "Steer before next queued input" })
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Also steer before next queued input" }),
|
||||
text: "Also steer before next queued input",
|
||||
})
|
||||
yield* session.synthetic({ sessionID, text: "Background completion before next queued input" })
|
||||
yield* Deferred.succeed(secondGate, undefined)
|
||||
yield* Fiber.join(first)
|
||||
streamGate = undefined
|
||||
|
|
@ -2651,12 +2683,14 @@ describe("SessionRunnerLLM", () => {
|
|||
"Queue first",
|
||||
"Steer before next queued input",
|
||||
"Also steer before next queued input",
|
||||
"Background completion before next queued input",
|
||||
])
|
||||
expect(userTexts(requests[3]!)).toEqual([
|
||||
"Start working",
|
||||
"Queue first",
|
||||
"Steer before next queued input",
|
||||
"Also steer before next queued input",
|
||||
"Background completion before next queued input",
|
||||
"Queue second",
|
||||
])
|
||||
}),
|
||||
|
|
@ -2673,8 +2707,8 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "First steer" }) })
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Second steer" }) })
|
||||
yield* session.prompt({ sessionID, text: "First steer" })
|
||||
yield* session.prompt({ sessionID, text: "Second steer" })
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
yield* Fiber.join(first)
|
||||
streamGate = undefined
|
||||
|
|
@ -2700,7 +2734,7 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const first = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Recover with this" }) })
|
||||
yield* session.prompt({ sessionID, text: "Recover with this" })
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
expect(yield* Fiber.join(first).pipe(Effect.flip)).toBe(streamFailure)
|
||||
|
||||
|
|
@ -2859,7 +2893,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const session = yield* setup
|
||||
yield* session.prompt({
|
||||
sessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Wait in queue" }),
|
||||
text: "Wait in queue",
|
||||
delivery: "queue",
|
||||
resume: false,
|
||||
})
|
||||
|
|
@ -2878,7 +2912,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const events = yield* EventV2.Service
|
||||
const defect = new Error("fail after prompt promotion")
|
||||
let fail = true
|
||||
yield* events.project(SessionEvent.PromptPromoted, () => (fail ? Effect.die(defect) : Effect.void))
|
||||
yield* events.project(SessionEvent.InputPromoted, () => (fail ? Effect.die(defect) : Effect.void))
|
||||
yield* admit(session, "Recover promoted input")
|
||||
|
||||
expect(yield* session.resume(sessionID).pipe(Effect.catchDefect(Effect.succeed))).toBe(defect)
|
||||
|
|
@ -2898,7 +2932,7 @@ describe("SessionRunnerLLM", () => {
|
|||
const session = yield* setup
|
||||
const events = yield* EventV2.Service
|
||||
yield* events.listen((event) =>
|
||||
event.type === SessionEvent.PromptPromoted.type
|
||||
event.type === SessionEvent.InputPromoted.type
|
||||
? Effect.die("fail after prompt promotion commits")
|
||||
: Effect.void,
|
||||
)
|
||||
|
|
@ -2918,7 +2952,7 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* admit(session, "Run first")
|
||||
yield* session.prompt({
|
||||
sessionID: otherSessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Run second" }),
|
||||
text: "Run second",
|
||||
resume: false,
|
||||
})
|
||||
|
||||
|
|
@ -2953,12 +2987,12 @@ describe("SessionRunnerLLM", () => {
|
|||
yield* insertSession(otherLongSessionID)
|
||||
yield* session.prompt({
|
||||
sessionID: longSessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Run long session" }),
|
||||
text: "Run long session",
|
||||
resume: false,
|
||||
})
|
||||
yield* session.prompt({
|
||||
sessionID: otherLongSessionID,
|
||||
prompt: PromptInput.Prompt.make({ text: "Run other long session" }),
|
||||
text: "Run other long session",
|
||||
resume: false,
|
||||
})
|
||||
|
||||
|
|
@ -3523,7 +3557,7 @@ describe("SessionRunnerLLM", () => {
|
|||
|
||||
const run = yield* session.resume(sessionID).pipe(Effect.forkChild)
|
||||
yield* Deferred.await(streamStarted)
|
||||
yield* session.prompt({ sessionID, prompt: PromptInput.Prompt.make({ text: "Change direction" }) })
|
||||
yield* session.prompt({ sessionID, text: "Change direction" })
|
||||
yield* Deferred.succeed(streamGate, undefined)
|
||||
yield* Fiber.join(run)
|
||||
streamGate = undefined
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue