opencode-overclock 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +206 -111
  2. package/package.json +2 -2
  3. package/src/bridge.ts +1 -0
  4. package/src/buddy/companion.ts +104 -5
  5. package/src/buddy/sprites.ts +4 -4
  6. package/src/buddy/tui.ts +175 -65
  7. package/src/core/bridge.ts +34 -0
  8. package/src/core/lifecycle.ts +53 -0
  9. package/src/core/policy.ts +128 -0
  10. package/src/core/summary.ts +33 -0
  11. package/src/core/types.ts +164 -0
  12. package/src/features/buddy.ts +1 -2
  13. package/src/features/guard.ts +168 -30
  14. package/src/features/index.ts +6 -4
  15. package/src/features/recovery.ts +143 -0
  16. package/src/features/sched.ts +147 -89
  17. package/src/features/tasks.ts +52 -18
  18. package/src/features/truncator.ts +99 -0
  19. package/src/features/usage.ts +26 -65
  20. package/src/index.ts +96 -67
  21. package/src/lib/busy.ts +1 -25
  22. package/src/lib/exec.ts +7 -0
  23. package/src/lib/inject.ts +10 -56
  24. package/src/lib/mirror.ts +13 -0
  25. package/src/lib/probe.ts +1 -15
  26. package/src/lib/state.ts +10 -39
  27. package/src/lib/tmux.ts +1 -0
  28. package/src/lib/ui.ts +208 -0
  29. package/src/merge.ts +2 -66
  30. package/src/platform/probe.ts +25 -0
  31. package/src/platform/process/exec.ts +76 -0
  32. package/src/platform/process/tmux.ts +60 -0
  33. package/src/platform/session/busy.ts +33 -0
  34. package/src/platform/session/inject.ts +82 -0
  35. package/src/platform/session/notify.ts +20 -0
  36. package/src/platform/storage/state.ts +77 -0
  37. package/src/platform/storage/store.ts +61 -0
  38. package/src/summary.ts +1 -0
  39. package/src/tools.ts +8 -244
  40. package/src/tui.ts +57 -186
  41. package/src/types.ts +1 -73
  42. package/src/v2/context.ts +470 -0
  43. package/src/v2/host.ts +117 -0
  44. package/src/v2/loader.ts +150 -0
  45. package/src/buddy/reactions.ts +0 -41
  46. package/src/buddy/types.ts +0 -30
  47. package/src/config.ts +0 -19
  48. package/src/features/checkpoints.ts +0 -128
  49. package/src/features/sandbox.ts +0 -104
  50. package/src/validate.ts +0 -197
@@ -0,0 +1,470 @@
1
+ /**
2
+ * @fileoverview OpenCode V2 Synthetic Context Bridge
3
+ *
4
+ * OpenCode V1 runtime environments only load plugins via `server(input, options)`
5
+ * returning an imperative `Hooks` object. OpenCode V2 introduces a declarative,
6
+ * transform-based domain architecture (`setup(context)`) where plugins register
7
+ * mutations on domain drafts (`agent`, `command`, `catalog`, `skill`, `reference`,
8
+ * `aisdk`, `plugin`).
9
+ *
10
+ * This module synthesizes a complete, spec-conforming `PluginContext` in memory on V1 hosts:
11
+ *
12
+ * - `agent.transform` --> Mutates `config.agent` and `config.default_agent` in V1 `config` hook.
13
+ * - `command.transform` --> Mutates `config.command` dictionary in V1 `config` hook.
14
+ * - `catalog.transform` --> Mutates `config.provider` and `config.model` in V1 `config` hook.
15
+ * - `skill.transform` --> Collects external skill sources into memory.
16
+ * - `reference.transform` --> Collects reference sources injected into `experimental.chat.system.transform`.
17
+ * - `aisdk.sdk` --> Intercepts LLM parameters via `chat.params` hook (`language` collected for spec compatibility).
18
+ * - `plugin.add / remove` --> Manages nested V2 plugins with scope-owned resource disposal.
19
+ */
20
+
21
+ import type {
22
+ AgentDraft,
23
+ AgentHooks,
24
+ AISDKHooks,
25
+ CatalogDraft,
26
+ CatalogHooks,
27
+ CatalogProviderRecord,
28
+ CommandDraft,
29
+ CommandHooks,
30
+ IntegrationDraft,
31
+ IntegrationHooks,
32
+ Plugin as V2Plugin,
33
+ PluginContext,
34
+ PluginDomain,
35
+ PluginOptions,
36
+ ReferenceDraft,
37
+ ReferenceHooks,
38
+ Registration,
39
+ Reload,
40
+ SkillDraft,
41
+ SkillHooks,
42
+ } from "@opencode-ai/plugin/v2/promise"
43
+ import type {
44
+ AgentV2Info,
45
+ CommandV2Info,
46
+ ModelV2Info,
47
+ ProviderV2Info,
48
+ ReferenceGitSource,
49
+ ReferenceLocalSource,
50
+ SkillV2Source,
51
+ } from "@opencode-ai/sdk/v2/types"
52
+
53
+ /** Disposer callback returned from registration or plugin lifecycle */
54
+ export type Disposer = () => Promise<void> | void
55
+
56
+ /** Internal state maintaining all active V2 transform registrations and collected metadata */
57
+ export interface V2RegistryState {
58
+ agentTransforms: Set<(draft: AgentDraft) => Promise<void> | void>
59
+ commandTransforms: Set<(draft: CommandDraft) => Promise<void> | void>
60
+ skillTransforms: Set<(draft: SkillDraft) => Promise<void> | void>
61
+ catalogTransforms: Set<(draft: CatalogDraft) => Promise<void> | void>
62
+ referenceTransforms: Set<(draft: ReferenceDraft) => Promise<void> | void>
63
+ integrationTransforms: Set<(draft: IntegrationDraft) => Promise<void> | void>
64
+ aisdkHooks: {
65
+ sdk: Set<(input: any) => Promise<void> | void>
66
+ language: Set<(input: any) => Promise<void> | void>
67
+ }
68
+ references: Map<string, ReferenceLocalSource | ReferenceGitSource>
69
+ skillSources: SkillV2Source[]
70
+ activePlugins: Map<string, { plugin: V2Plugin; disposers: Set<Disposer> }>
71
+ }
72
+
73
+ /** Operational handle exposing the synthetic context and configuration transform application */
74
+ export interface V2ContextHandle {
75
+ /** The root PluginContext passed to V2 setup() */
76
+ context: PluginContext
77
+ /** Internal transform and plugin state */
78
+ state: V2RegistryState
79
+ /** Applies all registered V2 transforms against the host's V1 config object */
80
+ applyConfigTransforms(cfg: Record<string, any>): Promise<void>
81
+ /** Creates a scoped child PluginContext for a specific plugin or options block */
82
+ scopedContext(options?: PluginOptions, tracker?: Set<Disposer>): PluginContext
83
+ /** Cleans up all registered transforms and active plugins */
84
+ dispose(): Promise<void>
85
+ }
86
+
87
+ /** Wraps a disposer into a V2 Registration object, tracking it in an optional scope set */
88
+ function makeRegistration(disposer: Disposer, tracker?: Set<Disposer>): Promise<Registration> {
89
+ const wrappedDisposer = async () => {
90
+ tracker?.delete(disposer)
91
+ await disposer()
92
+ }
93
+ tracker?.add(disposer)
94
+ return Promise.resolve({ dispose: wrappedDisposer })
95
+ }
96
+
97
+ /** Generic factory for transform-based domains implementing `transform(cb)` + `Reload` */
98
+ function createTransformDomain<TDraft>(
99
+ transforms: Set<(draft: TDraft) => Promise<void> | void>,
100
+ reload: Reload,
101
+ tracker?: Set<Disposer>,
102
+ ) {
103
+ return {
104
+ transform: (callback: (draft: TDraft) => Promise<void> | void) => {
105
+ transforms.add(callback)
106
+ return makeRegistration(() => {
107
+ transforms.delete(callback)
108
+ }, tracker)
109
+ },
110
+ ...reload,
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Creates a synthetic PluginContext implementing OpenCode V2 plugin specifications.
116
+ * Allows V2 plugins and hybrid plugins to run inside OpenCode V1 runtime sessions.
117
+ */
118
+ export function createV2PluginContext(
119
+ initialOptions: PluginOptions = {},
120
+ onReload?: () => Promise<void>,
121
+ ): V2ContextHandle {
122
+ const state: V2RegistryState = {
123
+ agentTransforms: new Set(),
124
+ commandTransforms: new Set(),
125
+ skillTransforms: new Set(),
126
+ catalogTransforms: new Set(),
127
+ referenceTransforms: new Set(),
128
+ integrationTransforms: new Set(),
129
+ aisdkHooks: { sdk: new Set(), language: new Set() },
130
+ references: new Map(),
131
+ skillSources: [],
132
+ activePlugins: new Map(),
133
+ }
134
+
135
+ const reloadHandler: Reload = {
136
+ reload: async () => {
137
+ if (onReload) await onReload()
138
+ },
139
+ }
140
+
141
+ function createScopedContext(
142
+ scopedOptions: PluginOptions = initialOptions,
143
+ tracker?: Set<Disposer>,
144
+ ): PluginContext {
145
+ const agent = createTransformDomain(state.agentTransforms, reloadHandler, tracker)
146
+ const command = createTransformDomain(state.commandTransforms, reloadHandler, tracker)
147
+ const skill = createTransformDomain(state.skillTransforms, reloadHandler, tracker)
148
+ const catalog = createTransformDomain(state.catalogTransforms, reloadHandler, tracker)
149
+ const reference = createTransformDomain(state.referenceTransforms, reloadHandler, tracker)
150
+
151
+ const integration: IntegrationHooks & Reload = {
152
+ ...createTransformDomain(state.integrationTransforms, reloadHandler, tracker),
153
+ connection: {
154
+ active: async () => undefined,
155
+ resolve: async () => undefined,
156
+ },
157
+ }
158
+
159
+ const aisdk: AISDKHooks = {
160
+ sdk: (callback) => {
161
+ state.aisdkHooks.sdk.add(callback)
162
+ return makeRegistration(() => {
163
+ state.aisdkHooks.sdk.delete(callback)
164
+ }, tracker)
165
+ },
166
+ language: (callback) => {
167
+ state.aisdkHooks.language.add(callback)
168
+ return makeRegistration(() => {
169
+ state.aisdkHooks.language.delete(callback)
170
+ }, tracker)
171
+ },
172
+ }
173
+
174
+ const pluginDomain: PluginDomain = {
175
+ add: async (p: V2Plugin) => {
176
+ if (state.activePlugins.has(p.id)) {
177
+ await pluginDomain.remove(p.id)
178
+ }
179
+ const pluginDisposers = new Set<Disposer>()
180
+ state.activePlugins.set(p.id, { plugin: p, disposers: pluginDisposers })
181
+
182
+ const pluginContext = createScopedContext(scopedOptions, pluginDisposers)
183
+
184
+ // Support effect-based v2 plugins if present, otherwise promise-based setup
185
+ if (typeof (p as any).effect === "function") {
186
+ const { runPromise } = await import("effect/Effect")
187
+ await runPromise((p as any).effect(pluginContext))
188
+ } else if (typeof p.setup === "function") {
189
+ await p.setup(pluginContext)
190
+ }
191
+ },
192
+ remove: async (id: string) => {
193
+ const entry = state.activePlugins.get(id)
194
+ if (!entry) return
195
+ for (const disposer of entry.disposers) {
196
+ try {
197
+ await disposer()
198
+ } catch (e) {
199
+ console.warn(`[overclock] v2: error disposing registration for plugin ${id}: ${e}`)
200
+ }
201
+ }
202
+ state.activePlugins.delete(id)
203
+ },
204
+ }
205
+
206
+ return {
207
+ options: scopedOptions,
208
+ agent,
209
+ aisdk,
210
+ catalog,
211
+ command,
212
+ integration,
213
+ plugin: pluginDomain,
214
+ reference,
215
+ skill,
216
+ }
217
+ }
218
+
219
+ const rootContext = createScopedContext(initialOptions)
220
+
221
+ async function applyConfigTransforms(cfg: Record<string, any>): Promise<void> {
222
+ if (!cfg || typeof cfg !== "object") return
223
+ await applyAgentTransforms(cfg, state.agentTransforms)
224
+ await applyCommandTransforms(cfg, state.commandTransforms)
225
+ await applyCatalogTransforms(cfg, state.catalogTransforms)
226
+ await applySkillTransforms(state.skillSources, state.skillTransforms)
227
+ await applyReferenceTransforms(state.references, state.referenceTransforms)
228
+ }
229
+
230
+ return {
231
+ context: rootContext,
232
+ state,
233
+ applyConfigTransforms,
234
+ scopedContext: createScopedContext,
235
+ dispose: async () => {
236
+ for (const [id, entry] of state.activePlugins) {
237
+ for (const disposer of entry.disposers) {
238
+ try {
239
+ await disposer()
240
+ } catch (e) {
241
+ console.warn(`[overclock] v2: error disposing ${id}: ${e}`)
242
+ }
243
+ }
244
+ }
245
+ state.activePlugins.clear()
246
+ state.agentTransforms.clear()
247
+ state.commandTransforms.clear()
248
+ state.catalogTransforms.clear()
249
+ state.skillTransforms.clear()
250
+ state.referenceTransforms.clear()
251
+ state.integrationTransforms.clear()
252
+ state.aisdkHooks.sdk.clear()
253
+ state.aisdkHooks.language.clear()
254
+ state.references.clear()
255
+ state.skillSources = []
256
+ },
257
+ }
258
+ }
259
+
260
+ /** Applies registered AgentDraft transforms onto `cfg.agent` and `cfg.default_agent` */
261
+ async function applyAgentTransforms(
262
+ cfg: Record<string, any>,
263
+ transforms: Set<(draft: AgentDraft) => Promise<void> | void>,
264
+ ): Promise<void> {
265
+ if (transforms.size === 0) return
266
+ cfg.agent = cfg.agent ?? {}
267
+
268
+ const draft: AgentDraft = {
269
+ list: () =>
270
+ Object.entries(cfg.agent).map(([id, val]: [string, any]) => ({
271
+ id,
272
+ mode: val?.mode ?? "all",
273
+ hidden: val?.hidden ?? false,
274
+ permissions: val?.permissions ?? [],
275
+ request: val?.request ?? {},
276
+ ...val,
277
+ })) as readonly AgentV2Info[],
278
+ get: (id) => {
279
+ const val = cfg.agent[id]
280
+ if (!val) return undefined
281
+ return {
282
+ id,
283
+ mode: val.mode ?? "all",
284
+ hidden: val.hidden ?? false,
285
+ permissions: val.permissions ?? [],
286
+ request: val.request ?? {},
287
+ ...val,
288
+ } as AgentV2Info
289
+ },
290
+ default: (id) => {
291
+ if (id) cfg.default_agent = id
292
+ else delete cfg.default_agent
293
+ },
294
+ update: (id, updater) => {
295
+ const current = draft.get(id) ?? {
296
+ id,
297
+ mode: "all",
298
+ hidden: false,
299
+ permissions: [],
300
+ request: {} as any,
301
+ }
302
+ updater(current)
303
+ cfg.agent[id] = { ...current }
304
+ },
305
+ remove: (id) => {
306
+ delete cfg.agent[id]
307
+ },
308
+ }
309
+
310
+ for (const transform of transforms) await transform(draft)
311
+ }
312
+
313
+ /** Applies registered CommandDraft transforms onto `cfg.command` */
314
+ async function applyCommandTransforms(
315
+ cfg: Record<string, any>,
316
+ transforms: Set<(draft: CommandDraft) => Promise<void> | void>,
317
+ ): Promise<void> {
318
+ if (transforms.size === 0) return
319
+ cfg.command = cfg.command ?? {}
320
+
321
+ const draft: CommandDraft = {
322
+ list: () =>
323
+ Object.entries(cfg.command).map(([name, val]: [string, any]) => ({
324
+ name,
325
+ template: val?.template ?? "",
326
+ description: val?.description,
327
+ agent: val?.agent,
328
+ model: val?.model,
329
+ subtask: val?.subtask,
330
+ ...val,
331
+ })) as readonly CommandV2Info[],
332
+ get: (name) => {
333
+ const val = cfg.command[name]
334
+ if (!val) return undefined
335
+ return {
336
+ name,
337
+ template: val.template ?? "",
338
+ description: val.description,
339
+ agent: val.agent,
340
+ model: val.model,
341
+ subtask: val.subtask,
342
+ ...val,
343
+ } as CommandV2Info
344
+ },
345
+ update: (name, updater) => {
346
+ const current = draft.get(name) ?? { name, template: "" }
347
+ updater(current)
348
+ cfg.command[name] = { ...current }
349
+ },
350
+ remove: (name) => {
351
+ delete cfg.command[name]
352
+ },
353
+ }
354
+
355
+ for (const transform of transforms) await transform(draft)
356
+ }
357
+
358
+ /** Applies registered CatalogDraft transforms onto `cfg.provider` and `cfg.model` */
359
+ async function applyCatalogTransforms(
360
+ cfg: Record<string, any>,
361
+ transforms: Set<(draft: CatalogDraft) => Promise<void> | void>,
362
+ ): Promise<void> {
363
+ if (transforms.size === 0) return
364
+ cfg.provider = cfg.provider ?? {}
365
+
366
+ const draft: CatalogDraft = {
367
+ provider: {
368
+ list: () =>
369
+ Object.entries(cfg.provider).map(([providerID, val]: [string, any]) => ({
370
+ provider: { id: providerID, name: val?.name ?? providerID, ...val } as ProviderV2Info,
371
+ models: new Map(
372
+ Object.entries(val?.models ?? {}).map(([mID, mVal]: [string, any]) => [
373
+ mID,
374
+ { id: mID, providerID, ...mVal } as ModelV2Info,
375
+ ]),
376
+ ),
377
+ })),
378
+ get: (providerID) => {
379
+ const val = cfg.provider[providerID]
380
+ if (!val) return undefined
381
+ return {
382
+ provider: { id: providerID, name: val.name ?? providerID, ...val } as ProviderV2Info,
383
+ models: new Map(
384
+ Object.entries(val.models ?? {}).map(([mID, mVal]: [string, any]) => [
385
+ mID,
386
+ { id: mID, providerID, ...mVal } as ModelV2Info,
387
+ ]),
388
+ ),
389
+ }
390
+ },
391
+ update: (providerID, updater) => {
392
+ const current = (cfg.provider[providerID] ?? {
393
+ id: providerID,
394
+ name: providerID,
395
+ }) as ProviderV2Info
396
+ updater(current)
397
+ cfg.provider[providerID] = { ...cfg.provider[providerID], ...current }
398
+ },
399
+ remove: (providerID) => {
400
+ delete cfg.provider[providerID]
401
+ },
402
+ },
403
+ model: {
404
+ get: (providerID, modelID) => {
405
+ const p = cfg.provider[providerID]
406
+ const m = p?.models?.[modelID]
407
+ return m ? ({ id: modelID, providerID, ...m } as ModelV2Info) : undefined
408
+ },
409
+ update: (providerID, modelID, updater) => {
410
+ cfg.provider[providerID] = cfg.provider[providerID] ?? {}
411
+ cfg.provider[providerID].models = cfg.provider[providerID].models ?? {}
412
+ const current = (cfg.provider[providerID].models[modelID] ?? {
413
+ id: modelID,
414
+ providerID,
415
+ }) as ModelV2Info
416
+ updater(current)
417
+ cfg.provider[providerID].models[modelID] = { ...current }
418
+ },
419
+ remove: (providerID, modelID) => {
420
+ if (cfg.provider[providerID]?.models) {
421
+ delete cfg.provider[providerID].models[modelID]
422
+ }
423
+ },
424
+ default: {
425
+ get: () => {
426
+ if (typeof cfg.model === "string") {
427
+ const [p, ...rest] = cfg.model.split("/")
428
+ return rest.length ? { providerID: p, modelID: rest.join("/") } : undefined
429
+ }
430
+ if (typeof cfg.model === "object" && cfg.model) {
431
+ return { providerID: cfg.model.providerID, modelID: cfg.model.modelID }
432
+ }
433
+ return undefined
434
+ },
435
+ set: (providerID, modelID) => {
436
+ cfg.model = `${providerID}/${modelID}`
437
+ },
438
+ },
439
+ },
440
+ }
441
+
442
+ for (const transform of transforms) await transform(draft)
443
+ }
444
+
445
+ /** Collects skill sources from registered SkillDraft transforms */
446
+ async function applySkillTransforms(
447
+ skillSources: SkillV2Source[],
448
+ transforms: Set<(draft: SkillDraft) => Promise<void> | void>,
449
+ ): Promise<void> {
450
+ if (transforms.size === 0) return
451
+ const draft: SkillDraft = {
452
+ source: (src) => skillSources.push(src),
453
+ list: () => skillSources,
454
+ }
455
+ for (const transform of transforms) await transform(draft)
456
+ }
457
+
458
+ /** Collects reference sources from registered ReferenceDraft transforms */
459
+ async function applyReferenceTransforms(
460
+ references: Map<string, ReferenceLocalSource | ReferenceGitSource>,
461
+ transforms: Set<(draft: ReferenceDraft) => Promise<void> | void>,
462
+ ): Promise<void> {
463
+ if (transforms.size === 0) return
464
+ const draft: ReferenceDraft = {
465
+ add: (name, source) => references.set(name, source),
466
+ remove: (name) => references.delete(name),
467
+ list: () => [...references.entries()],
468
+ }
469
+ for (const transform of transforms) await transform(draft)
470
+ }
package/src/v2/host.ts ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @fileoverview OpenCode V2 Host Engine
3
+ *
4
+ * Coordinates the execution of OpenCode V2 plugins inside an OpenCode V1 runtime session.
5
+ * Manages plugin loading, setup invocation, and generates the V1 `Hooks` that integrate
6
+ * V2 domain transforms into the host's lifecycle:
7
+ *
8
+ * - `config`: Executes `agent`, `command`, `catalog`, `skill`, and `reference` draft transforms against host configuration and context state.
9
+ * - `experimental.chat.system.transform`: Injects reference documents into the system prompt.
10
+ * - `chat.params`: Forwards model/provider parameters to registered AI-SDK middleware hooks.
11
+ * - `dispose`: Gracefully tears down all active V2 plugins and their scoped registrations.
12
+ */
13
+
14
+ import type { Hooks, PluginInput } from "@opencode-ai/plugin"
15
+ import type { PluginContext } from "@opencode-ai/plugin/v2/promise"
16
+ import { createV2PluginContext, type V2ContextHandle } from "./context.ts"
17
+ import { loadV2Plugin, type V2PluginSpec } from "./loader.ts"
18
+ import type { OverclockOptions } from "../types.ts"
19
+
20
+ /**
21
+ * Controller interface for running V2 plugins on a V1 host.
22
+ */
23
+ export interface V2Host {
24
+ /** The low-level synthetic context handle */
25
+ handle: V2ContextHandle
26
+ /** The root PluginContext passed to V2 setup methods */
27
+ context: PluginContext
28
+ /** Loads and initializes a list of V2 plugin specifiers */
29
+ loadPlugins(specs?: V2PluginSpec[]): Promise<string[]>
30
+ /** Executes a feature's or hybrid plugin's V2 setup function */
31
+ runSetup(
32
+ setupFn: (ctx: PluginContext, opts?: Record<string, unknown>) => Promise<void> | void,
33
+ opts?: Record<string, unknown>,
34
+ ): Promise<void>
35
+ /** Creates V1 Hooks integrating all V2 transforms into the OpenCode V1 engine */
36
+ createHooks(): Partial<Hooks>
37
+ }
38
+
39
+ /**
40
+ * Creates a V2Host instance that adapts OpenCode V2 plugin transforms
41
+ * into standard OpenCode V1 runtime Hooks.
42
+ */
43
+ export function createV2Host(ctx: PluginInput, options: OverclockOptions = {}): V2Host {
44
+ const handle = createV2PluginContext(options)
45
+
46
+ const host: V2Host = {
47
+ handle,
48
+ context: handle.context,
49
+
50
+ loadPlugins: async (specs: V2PluginSpec[] = []) => {
51
+ const loaded: string[] = []
52
+ for (const spec of specs) {
53
+ const id = await loadV2Plugin(spec, ctx.directory, handle)
54
+ if (id) loaded.push(id)
55
+ }
56
+ return loaded
57
+ },
58
+
59
+ runSetup: async (setupFn, opts = {}) => {
60
+ try {
61
+ await setupFn(handle.context, opts)
62
+ } catch (e) {
63
+ console.warn(`[overclock] v2: setup execution failed: ${e}`)
64
+ }
65
+ },
66
+
67
+ createHooks: (): Partial<Hooks> => {
68
+ const hooks: Partial<Hooks> = {
69
+ config: async (cfg: any) => {
70
+ await handle.applyConfigTransforms(cfg)
71
+ },
72
+
73
+ "experimental.chat.system.transform": async (_input, output) => {
74
+ if (handle.state.references.size === 0) return
75
+ const refLines: string[] = ["# References"]
76
+ for (const [name, ref] of handle.state.references) {
77
+ if (ref.type === "local") {
78
+ refLines.push(`- ${name}: ${ref.path}${ref.description ? ` (${ref.description})` : ""}`)
79
+ } else if (ref.type === "git") {
80
+ refLines.push(
81
+ `- ${name}: ${ref.repository} (${ref.branch ?? "HEAD"})${ref.description ? ` (${ref.description})` : ""}`,
82
+ )
83
+ }
84
+ }
85
+ output.system.push(refLines.join("\n"))
86
+ },
87
+
88
+ "chat.params": async (input, output) => {
89
+ if (handle.state.aisdkHooks.sdk.size === 0) return
90
+ const sdkPayload = {
91
+ model: {
92
+ id: (input.model as any)?.id ?? (input.model as any)?.name ?? "unknown",
93
+ providerID: (input.provider as any)?.id ?? "unknown",
94
+ },
95
+ package: "@ai-sdk/provider",
96
+ options: output.options ?? {},
97
+ }
98
+ for (const hook of handle.state.aisdkHooks.sdk) {
99
+ try {
100
+ await hook(sdkPayload)
101
+ } catch (e) {
102
+ console.warn(`[overclock] v2: aisdk.sdk hook failed: ${e}`)
103
+ }
104
+ }
105
+ },
106
+
107
+ dispose: async () => {
108
+ await handle.dispose()
109
+ },
110
+ }
111
+
112
+ return hooks
113
+ },
114
+ }
115
+
116
+ return host
117
+ }