Skip to content

Commit 7e2011d

Browse files
committed
fix(sdk): stabilize session event identities
1 parent 9180058 commit 7e2011d

18 files changed

Lines changed: 1762 additions & 2613 deletions

File tree

packages/client/src/promise/generated/types.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,7 +1394,7 @@ export type SessionLogOutput =
13941394
readonly type: "session.text.started"
13951395
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
13961396
readonly location?: { readonly directory: string; readonly workspaceID?: string }
1397-
readonly data: { readonly sessionID: string; readonly assistantMessageID: string }
1397+
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly ordinal: number }
13981398
}
13991399
| {
14001400
readonly id: string
@@ -1403,7 +1403,12 @@ export type SessionLogOutput =
14031403
readonly type: "session.text.ended"
14041404
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
14051405
readonly location?: { readonly directory: string; readonly workspaceID?: string }
1406-
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly text: string }
1406+
readonly data: {
1407+
readonly sessionID: string
1408+
readonly assistantMessageID: string
1409+
readonly ordinal: number
1410+
readonly text: string
1411+
}
14071412
}
14081413
| {
14091414
readonly id: string
@@ -1415,6 +1420,7 @@ export type SessionLogOutput =
14151420
readonly data: {
14161421
readonly sessionID: string
14171422
readonly assistantMessageID: string
1423+
readonly ordinal: number
14181424
readonly state?: { readonly [x: string]: unknown }
14191425
}
14201426
}
@@ -1428,6 +1434,7 @@ export type SessionLogOutput =
14281434
readonly data: {
14291435
readonly sessionID: string
14301436
readonly assistantMessageID: string
1437+
readonly ordinal: number
14311438
readonly text: string
14321439
readonly state?: { readonly [x: string]: unknown }
14331440
}
@@ -4687,15 +4694,20 @@ export type EventSubscribeOutput =
46874694
readonly type: "session.text.started"
46884695
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
46894696
readonly location?: { readonly directory: string; readonly workspaceID?: string }
4690-
readonly data: { readonly sessionID: string; readonly assistantMessageID: string }
4697+
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly ordinal: number }
46914698
}
46924699
| {
46934700
readonly id: string
46944701
readonly created: number
46954702
readonly metadata?: { readonly [x: string]: unknown }
46964703
readonly type: "session.text.delta"
46974704
readonly location?: { readonly directory: string; readonly workspaceID?: string }
4698-
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly delta: string }
4705+
readonly data: {
4706+
readonly sessionID: string
4707+
readonly assistantMessageID: string
4708+
readonly ordinal: number
4709+
readonly delta: string
4710+
}
46994711
}
47004712
| {
47014713
readonly id: string
@@ -4704,7 +4716,12 @@ export type EventSubscribeOutput =
47044716
readonly type: "session.text.ended"
47054717
readonly durable: { readonly aggregateID: string; readonly seq: number; readonly version: number }
47064718
readonly location?: { readonly directory: string; readonly workspaceID?: string }
4707-
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly text: string }
4719+
readonly data: {
4720+
readonly sessionID: string
4721+
readonly assistantMessageID: string
4722+
readonly ordinal: number
4723+
readonly text: string
4724+
}
47084725
}
47094726
| {
47104727
readonly id: string
@@ -4716,6 +4733,7 @@ export type EventSubscribeOutput =
47164733
readonly data: {
47174734
readonly sessionID: string
47184735
readonly assistantMessageID: string
4736+
readonly ordinal: number
47194737
readonly state?: { readonly [x: string]: unknown }
47204738
}
47214739
}
@@ -4725,7 +4743,12 @@ export type EventSubscribeOutput =
47254743
readonly metadata?: { readonly [x: string]: unknown }
47264744
readonly type: "session.reasoning.delta"
47274745
readonly location?: { readonly directory: string; readonly workspaceID?: string }
4728-
readonly data: { readonly sessionID: string; readonly assistantMessageID: string; readonly delta: string }
4746+
readonly data: {
4747+
readonly sessionID: string
4748+
readonly assistantMessageID: string
4749+
readonly ordinal: number
4750+
readonly delta: string
4751+
}
47294752
}
47304753
| {
47314754
readonly id: string
@@ -4737,6 +4760,7 @@ export type EventSubscribeOutput =
47374760
readonly data: {
47384761
readonly sessionID: string
47394762
readonly assistantMessageID: string
4763+
readonly ordinal: number
47404764
readonly text: string
47414765
readonly state?: { readonly [x: string]: unknown }
47424766
}

packages/core/src/permission.ts

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -130,19 +130,13 @@ const layer = Layer.effect(
130130
const sessions = yield* SessionStore.Service
131131
const saved = yield* PermissionSaved.Service
132132
const pending = new Map<ID, Pending>()
133+
const rejected = (request: Request) =>
134+
new RejectedError({ permission: request.action, resources: [...request.resources] })
133135

134136
yield* Effect.addFinalizer(() =>
135-
Effect.forEach(
136-
pending.values(),
137-
(item) =>
138-
Deferred.fail(
139-
item.deferred,
140-
new RejectedError({ permission: item.request.action, resources: [...item.request.resources] }),
141-
),
142-
{
143-
discard: true,
144-
},
145-
).pipe(
137+
Effect.forEach(pending.values(), (item) => Deferred.fail(item.deferred, rejected(item.request)), {
138+
discard: true,
139+
}).pipe(
146140
Effect.ensuring(
147141
Effect.sync(() => {
148142
pending.clear()
@@ -253,12 +247,7 @@ const layer = Layer.effect(
253247
if (input.reply === "reject") {
254248
yield* Deferred.fail(
255249
existing.deferred,
256-
input.message
257-
? new CorrectedError({ feedback: input.message })
258-
: new RejectedError({
259-
permission: existing.request.action,
260-
resources: [...existing.request.resources],
261-
}),
250+
input.message ? new CorrectedError({ feedback: input.message }) : rejected(existing.request),
262251
)
263252
pending.delete(input.requestID)
264253
for (const [id, item] of pending) {
@@ -268,10 +257,7 @@ const layer = Layer.effect(
268257
requestID: item.request.id,
269258
reply: "reject",
270259
})
271-
yield* Deferred.fail(
272-
item.deferred,
273-
new RejectedError({ permission: item.request.action, resources: [...item.request.resources] }),
274-
)
260+
yield* Deferred.fail(item.deferred, rejected(item.request))
275261
pending.delete(id)
276262
}
277263
return

packages/core/src/session/runner/publish-llm-event.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,28 +98,30 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
9898

9999
const fragments = (
100100
name: string,
101-
ended: (id: string, value: string, state?: Record<string, unknown>) => Effect.Effect<void>,
101+
ended: (id: string, value: string, ordinal: number, state?: Record<string, unknown>) => Effect.Effect<void>,
102102
single = false,
103103
) => {
104-
const chunks = new Map<string, string[]>()
104+
const chunks = new Map<string, { readonly ordinal: number; readonly values: string[] }>()
105+
let nextOrdinal = 0
105106
const start = (id: string) =>
106107
Effect.suspend(() => {
107108
if (chunks.has(id)) return Effect.die(new Error(`Duplicate ${name} start: ${id}`))
108109
if (single && chunks.size > 0) return Effect.die(new Error(`${name} start before end: ${id}`))
109-
chunks.set(id, [])
110-
return Effect.void
110+
const ordinal = nextOrdinal++
111+
chunks.set(id, { ordinal, values: [] })
112+
return Effect.succeed(ordinal)
111113
})
112114
const append = (id: string, value: string) =>
113115
Effect.suspend(() => {
114116
const current = chunks.get(id)
115117
if (!current) return Effect.die(new Error(`${name} delta before start: ${id}`))
116-
current.push(value)
117-
return Effect.void
118+
current.values.push(value)
119+
return Effect.succeed(current.ordinal)
118120
})
119121
const end = Effect.fnUntraced(function* (id: string, state?: Record<string, unknown>) {
120122
const current = chunks.get(id)
121123
if (!current) return yield* Effect.die(new Error(`${name} end before start: ${id}`))
122-
yield* ended(id, current.join(""), state)
124+
yield* ended(id, current.values.join(""), current.ordinal, state)
123125
chunks.delete(id)
124126
})
125127
const flush = Effect.fnUntraced(function* () {
@@ -130,23 +132,25 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
130132

131133
const text = fragments(
132134
"text",
133-
(_textID, value) =>
135+
(_textID, value, ordinal) =>
134136
Effect.gen(function* () {
135137
yield* events.publish(SessionEvent.Text.Ended, {
136138
sessionID: input.sessionID,
137139
assistantMessageID: yield* currentAssistantMessageID(),
140+
ordinal,
138141
text: value,
139142
})
140143
}),
141144
true,
142145
)
143146
const reasoning = fragments(
144147
"reasoning",
145-
(_reasoningID, value, state) =>
148+
(_reasoningID, value, ordinal, state) =>
146149
Effect.gen(function* () {
147150
yield* events.publish(SessionEvent.Reasoning.Ended, {
148151
sessionID: input.sessionID,
149152
assistantMessageID: yield* currentAssistantMessageID(),
153+
ordinal,
150154
text: value,
151155
state,
152156
})
@@ -259,17 +263,19 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
259263
return
260264
case "text-start":
261265
retryEvidence = true
262-
yield* text.start(event.id)
266+
const startedTextOrdinal = yield* text.start(event.id)
263267
yield* events.publish(SessionEvent.Text.Started, {
264268
sessionID: input.sessionID,
265269
assistantMessageID: yield* startAssistant(),
270+
ordinal: startedTextOrdinal,
266271
})
267272
return
268273
case "text-delta":
269-
yield* text.append(event.id, event.text)
274+
const deltaTextOrdinal = yield* text.append(event.id, event.text)
270275
yield* events.publish(SessionEvent.Text.Delta, {
271276
sessionID: input.sessionID,
272277
assistantMessageID: yield* currentAssistantMessageID(),
278+
ordinal: deltaTextOrdinal,
273279
delta: event.text,
274280
})
275281
return
@@ -278,18 +284,20 @@ export const createLLMEventPublisher = (events: EventV2.Interface, input: Input)
278284
return
279285
case "reasoning-start":
280286
retryEvidence = true
281-
yield* reasoning.start(event.id)
287+
const startedReasoningOrdinal = yield* reasoning.start(event.id)
282288
yield* events.publish(SessionEvent.Reasoning.Started, {
283289
sessionID: input.sessionID,
284290
assistantMessageID: yield* startAssistant(),
291+
ordinal: startedReasoningOrdinal,
285292
state: providerState(event.providerMetadata),
286293
})
287294
return
288295
case "reasoning-delta":
289-
yield* reasoning.append(event.id, event.text)
296+
const deltaReasoningOrdinal = yield* reasoning.append(event.id, event.text)
290297
yield* events.publish(SessionEvent.Reasoning.Delta, {
291298
sessionID: input.sessionID,
292299
assistantMessageID: yield* currentAssistantMessageID(),
300+
ordinal: deltaReasoningOrdinal,
293301
delta: event.text,
294302
})
295303
return

packages/core/test/session-projector.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,7 @@ describe("SessionProjector", () => {
592592
yield* service.publish(SessionEvent.Text.Started, {
593593
sessionID,
594594
assistantMessageID: SessionMessage.ID.make("msg_assistant_completed"),
595+
ordinal: 0,
595596
})
596597

597598
const rows = yield* db

packages/core/test/tool-shell.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,12 @@ const executionNode = makeGlobalNode({
9292
yield* events.publish(SessionEvent.Text.Started, {
9393
sessionID: id,
9494
assistantMessageID,
95+
ordinal: 0,
9596
})
9697
yield* events.publish(SessionEvent.Text.Ended, {
9798
sessionID: id,
9899
assistantMessageID,
100+
ordinal: 0,
99101
text: "ok",
100102
})
101103
yield* events.publish(SessionEvent.Step.Ended, {

packages/core/test/tool-subagent.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@ const executionNode = makeGlobalNode({
5858
yield* events.publish(SessionEvent.Text.Started, {
5959
sessionID,
6060
assistantMessageID,
61+
ordinal: 0,
6162
})
6263
yield* events.publish(SessionEvent.Text.Ended, {
6364
sessionID,
6465
assistantMessageID,
66+
ordinal: 0,
6567
text: childText,
6668
})
6769
yield* events.publish(SessionEvent.Step.Ended, {

0 commit comments

Comments
 (0)