Skip to content

Commit 75813dc

Browse files
authored
Merge branch 'dev' into fix/lingering-list-references
2 parents 4ddcb01 + 1ee712e commit 75813dc

6 files changed

Lines changed: 120 additions & 12 deletions

File tree

packages/opencode/src/cli/cmd/tui/context/sync.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
467467
return store.status
468468
},
469469
get ready() {
470-
return true
471470
if (process.env.OPENCODE_FAST_BOOT) return true
472471
return store.status !== "loading"
473472
},

packages/opencode/src/npm/index.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,17 @@ export const layer = Layer.effect(
150150
if (!canWrite) return
151151

152152
const add = input?.add.map((pkg) => [pkg.name, pkg.version].filter(Boolean).join("@")) ?? []
153-
yield* Effect.gen(function* () {
154-
const nodeModulesExists = yield* afs.existsSafe(path.join(dir, "node_modules"))
155-
if (!nodeModulesExists) {
156-
yield* reify({ add, dir })
157-
return
158-
}
159-
}).pipe(Effect.withSpan("Npm.checkNodeModules"))
153+
if (
154+
yield* Effect.gen(function* () {
155+
const nodeModulesExists = yield* afs.existsSafe(path.join(dir, "node_modules"))
156+
if (!nodeModulesExists) {
157+
yield* reify({ add, dir })
158+
return true
159+
}
160+
return false
161+
}).pipe(Effect.withSpan("Npm.checkNodeModules"))
162+
)
163+
return
160164

161165
yield* Effect.gen(function* () {
162166
const pkg = yield* afs.readJson(path.join(dir, "package.json")).pipe(Effect.orElseSucceed(() => ({})))

packages/opencode/src/v2/session-entry-stepper.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { castDraft, produce, type WritableDraft } from "immer"
1+
import { produce, type WritableDraft } from "immer"
22
import { SessionEvent } from "./session-event"
33
import { SessionEntry } from "./session-entry"
44

@@ -235,7 +235,15 @@ export function stepWith<Result>(adapter: Adapter<Result>, event: SessionEvent.E
235235
)
236236
}
237237
},
238-
retried: () => {},
238+
retried: (event) => {
239+
if (currentAssistant) {
240+
adapter.updateAssistant(
241+
produce(currentAssistant, (draft) => {
242+
draft.retries = [...(draft.retries ?? []), SessionEntry.AssistantRetry.fromEvent(event)]
243+
}),
244+
)
245+
}
246+
},
239247
compacted: (event) => {
240248
adapter.appendEntry(SessionEntry.Compaction.fromEvent(event))
241249
},

packages/opencode/src/v2/session-entry.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,24 @@ export class AssistantReasoning extends Schema.Class<AssistantReasoning>("Sessio
104104
text: Schema.String,
105105
}) {}
106106

107+
export class AssistantRetry extends Schema.Class<AssistantRetry>("Session.Entry.Assistant.Retry")({
108+
attempt: Schema.Number,
109+
error: SessionEvent.RetryError,
110+
time: Schema.Struct({
111+
created: Schema.DateTimeUtc,
112+
}),
113+
}) {
114+
static fromEvent(event: SessionEvent.Retried) {
115+
return new AssistantRetry({
116+
attempt: event.attempt,
117+
error: event.error,
118+
time: {
119+
created: event.timestamp,
120+
},
121+
})
122+
}
123+
}
124+
107125
export const AssistantContent = Schema.Union([AssistantText, AssistantReasoning, AssistantTool]).pipe(
108126
Schema.toTaggedUnion("type"),
109127
)
@@ -113,6 +131,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Entry.Assistant"
113131
...Base,
114132
type: Schema.Literal("assistant"),
115133
content: AssistantContent.pipe(Schema.Array),
134+
retries: AssistantRetry.pipe(Schema.Array, Schema.optional),
116135
cost: Schema.Number.pipe(Schema.optional),
117136
tokens: Schema.Struct({
118137
input: Schema.Number,
@@ -137,6 +156,7 @@ export class Assistant extends Schema.Class<Assistant>("Session.Entry.Assistant"
137156
created: event.timestamp,
138157
},
139158
content: [],
159+
retries: [],
140160
})
141161
}
142162
}

packages/opencode/src/v2/session-event.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ export namespace SessionEvent {
5353
source: Source.pipe(Schema.optional),
5454
}) {}
5555

56+
export class RetryError extends Schema.Class<RetryError>("Session.Event.Retry.Error")({
57+
message: Schema.String,
58+
statusCode: Schema.Number.pipe(Schema.optional),
59+
isRetryable: Schema.Boolean,
60+
responseHeaders: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
61+
responseBody: Schema.String.pipe(Schema.optional),
62+
metadata: Schema.Record(Schema.String, Schema.String).pipe(Schema.optional),
63+
}) {}
64+
5665
export class Prompt extends Schema.Class<Prompt>("Session.Event.Prompt")({
5766
...Base,
5867
type: Schema.Literal("prompt"),
@@ -386,14 +395,16 @@ export namespace SessionEvent {
386395
export class Retried extends Schema.Class<Retried>("Session.Event.Retried")({
387396
...Base,
388397
type: Schema.Literal("retried"),
389-
error: Schema.String,
398+
attempt: Schema.Number,
399+
error: RetryError,
390400
}) {
391-
static create(input: BaseInput & { error: string }) {
401+
static create(input: BaseInput & { attempt: number; error: RetryError }) {
392402
return new Retried({
393403
id: input.id ?? ID.create(),
394404
type: "retried",
395405
timestamp: input.timestamp ?? DateTime.makeUnsafe(Date.now()),
396406
metadata: input.metadata,
407+
attempt: input.attempt,
397408
error: input.error,
398409
})
399410
}

packages/opencode/test/session/session-entry-stepper.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,24 @@ function assistant() {
2727
type: "assistant",
2828
time: { created: time(0) },
2929
content: [],
30+
retries: [],
31+
})
32+
}
33+
34+
function retryError(message: string) {
35+
return new SessionEvent.RetryError({
36+
message,
37+
isRetryable: true,
38+
})
39+
}
40+
41+
function retry(attempt: number, message: string, created: number) {
42+
return new SessionEntry.AssistantRetry({
43+
attempt,
44+
error: retryError(message),
45+
time: {
46+
created: time(created),
47+
},
3048
})
3149
}
3250

@@ -78,6 +96,12 @@ function tool(state: SessionEntryStepper.MemoryState, callID: string) {
7896
return tools(state).find((x) => x.callID === callID)
7997
}
8098

99+
function retriesOf(state: SessionEntryStepper.MemoryState) {
100+
const entry = last(state)
101+
if (!entry) return []
102+
return entry.retries ?? []
103+
}
104+
81105
function adapterStore() {
82106
return {
83107
committed: [] as SessionEntry.Entry[],
@@ -168,6 +192,33 @@ describe("session-entry-stepper", () => {
168192
])
169193
expect(store.committed[0].time.completed).toEqual(time(7))
170194
})
195+
196+
test("aggregates retry events onto the current assistant", () => {
197+
const store = adapterStore()
198+
store.committed.push(assistant())
199+
200+
SessionEntryStepper.stepWith(
201+
adapterFor(store),
202+
SessionEvent.Retried.create({
203+
attempt: 1,
204+
error: retryError("rate limited"),
205+
timestamp: time(1),
206+
}),
207+
)
208+
SessionEntryStepper.stepWith(
209+
adapterFor(store),
210+
SessionEvent.Retried.create({
211+
attempt: 2,
212+
error: retryError("provider overloaded"),
213+
timestamp: time(2),
214+
}),
215+
)
216+
217+
expect(store.committed[0]?.type).toBe("assistant")
218+
if (store.committed[0]?.type !== "assistant") return
219+
220+
expect(store.committed[0].retries).toEqual([retry(1, "rate limited", 1), retry(2, "provider overloaded", 2)])
221+
})
171222
})
172223

173224
describe("memory", () => {
@@ -231,6 +282,21 @@ describe("session-entry-stepper", () => {
231282

232283
expect(reasons(state)).toEqual([{ type: "reasoning", text: "final" }])
233284
})
285+
286+
test("stepWith through memory records retries", () => {
287+
const state = active()
288+
289+
SessionEntryStepper.stepWith(
290+
SessionEntryStepper.memory(state),
291+
SessionEvent.Retried.create({
292+
attempt: 1,
293+
error: retryError("rate limited"),
294+
timestamp: time(1),
295+
}),
296+
)
297+
298+
expect(retriesOf(state)).toEqual([retry(1, "rate limited", 1)])
299+
})
234300
})
235301

236302
describe("step", () => {

0 commit comments

Comments
 (0)