@cortexkit/aft-opencode 0.18.4 → 0.19.1
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.
- package/dist/bg-notifications.d.ts.map +1 -1
- package/dist/configure-warnings.d.ts +31 -0
- package/dist/configure-warnings.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18049 -17794
- package/dist/logger.d.ts +18 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/notifications.d.ts.map +1 -1
- package/dist/shared/last-assistant-model.d.ts +51 -0
- package/dist/shared/last-assistant-model.d.ts.map +1 -0
- package/dist/shared/session-directory.d.ts +32 -0
- package/dist/shared/session-directory.d.ts.map +1 -0
- package/dist/tools/_shared.d.ts +26 -4
- package/dist/tools/_shared.d.ts.map +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/hoisted.d.ts +9 -1
- package/dist/tools/hoisted.d.ts.map +1 -1
- package/dist/tools/reading.d.ts +8 -2
- package/dist/tools/reading.d.ts.map +1 -1
- package/dist/tui.js +7 -6
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow-hints.d.ts.map +1 -1
- package/package.json +7 -6
- package/src/logger.ts +19 -0
- package/src/shared/last-assistant-model.ts +179 -0
- package/src/shared/session-directory.ts +152 -0
- package/dist/bridge.d.ts +0 -122
- package/dist/bridge.d.ts.map +0 -1
- package/dist/downloader.d.ts +0 -35
- package/dist/downloader.d.ts.map +0 -1
- package/dist/onnx-runtime.d.ts +0 -53
- package/dist/onnx-runtime.d.ts.map +0 -1
- package/dist/platform.d.ts +0 -21
- package/dist/platform.d.ts.map +0 -1
- package/dist/pool.d.ts +0 -66
- package/dist/pool.d.ts.map +0 -1
- package/dist/resolver.d.ts +0 -36
- package/dist/resolver.d.ts.map +0 -1
- package/dist/shared/last-user-model.d.ts +0 -38
- package/dist/shared/last-user-model.d.ts.map +0 -1
- package/dist/shared/url-fetch.d.ts +0 -33
- package/dist/shared/url-fetch.d.ts.map +0 -1
- package/src/shared/last-user-model.ts +0 -133
- package/src/shared/url-fetch.ts +0 -465
package/dist/logger.d.ts
CHANGED
|
@@ -11,4 +11,22 @@ export declare function sessionLog(sessionId: string | undefined, message: strin
|
|
|
11
11
|
export declare function sessionWarn(sessionId: string | undefined, message: string, data?: unknown): void;
|
|
12
12
|
export declare function sessionError(sessionId: string | undefined, message: string, data?: unknown): void;
|
|
13
13
|
export declare function getLogFilePath(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Adapter that exposes this logger as a {@link import("@cortexkit/aft-bridge").Logger}
|
|
16
|
+
* for the shared bridge package. The bridge package never knows about this log
|
|
17
|
+
* file or the `[aft-plugin]` tag — it just calls `log/warn/error` and we map
|
|
18
|
+
* its `LogMeta.sessionId` into our internal session-prefix shape.
|
|
19
|
+
*/
|
|
20
|
+
export declare const bridgeLogger: {
|
|
21
|
+
log(message: string, meta?: {
|
|
22
|
+
sessionId?: string;
|
|
23
|
+
}): void;
|
|
24
|
+
warn(message: string, meta?: {
|
|
25
|
+
sessionId?: string;
|
|
26
|
+
}): void;
|
|
27
|
+
error(message: string, meta?: {
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
}): void;
|
|
30
|
+
getLogFilePath: () => string;
|
|
31
|
+
};
|
|
14
32
|
//# sourceMappingURL=logger.d.ts.map
|
package/dist/logger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAuEA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEzD;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE1D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE/F;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEhG;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEjG;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC"}
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAuEA,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEzD;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE1D;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAE/F;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEhG;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAEjG;AAED,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY;iBACV,MAAM,SAAS;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;kBAGpC,MAAM,SAAS;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;mBAGpC,MAAM,SAAS;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;;CAIrD,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../src/notifications.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAyOH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,yBAAyB,GAAG,uBAAuB,GAAG,oBAAoB,CAAC;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW3F;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C1F;AAED;;;;;GAKG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,mBAAmB,EACzB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAwED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,gBAAgB,EAAE,GAC3B,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C9E"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache-busting fix for prompt prefix cache eviction.
|
|
3
|
+
*
|
|
4
|
+
* PROBLEM: Every PromptInput we send to OpenCode (notifications, idle
|
|
5
|
+
* wakeups, ignored messages) creates a new user message. OpenCode's
|
|
6
|
+
* `createUserMessage` resolves variant relative to the chosen agent. If
|
|
7
|
+
* we don't pass model/variant, defaults take over and bust the provider
|
|
8
|
+
* prefix cache that the previous assistant turn warmed.
|
|
9
|
+
*
|
|
10
|
+
* APPROACH: Mirror what `opencode-xtra` does in production. Read recent
|
|
11
|
+
* messages from `client.session.messages()`, prefer the most recent
|
|
12
|
+
* assistant message, fall back to any role, MERGE across messages so
|
|
13
|
+
* partial fields (e.g. agent set on user message but not assistant) are
|
|
14
|
+
* filled in, and read BOTH the flat shape (`info.providerID`) used by
|
|
15
|
+
* AssistantMessage and the nested shape (`info.model.providerID`) used
|
|
16
|
+
* by UserMessage.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTANT: This context is only meaningful for callers that DO trigger
|
|
19
|
+
* LLM inference (e.g. background-bash idle wakes with `noReply: false`).
|
|
20
|
+
* Callers using `noReply: true` (one-off ignored messages, warnings,
|
|
21
|
+
* announcements) never trigger inference, so they don't need model or
|
|
22
|
+
* variant — the model/variant pass-through there is unnecessary AND has
|
|
23
|
+
* been observed to crash OpenCode under some configurations. Limit
|
|
24
|
+
* model/variant pass-through to wake-style calls.
|
|
25
|
+
*/
|
|
26
|
+
export interface ResolvedPromptContext {
|
|
27
|
+
agent?: string;
|
|
28
|
+
model?: {
|
|
29
|
+
providerID: string;
|
|
30
|
+
modelID: string;
|
|
31
|
+
};
|
|
32
|
+
variant?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read recent messages from the OpenCode session and resolve the most
|
|
36
|
+
* recent assistant prompt context. Falls back to user messages if no
|
|
37
|
+
* assistant has the field. Merges across messages so partial fields are
|
|
38
|
+
* filled in. Returns null if no usable context is found.
|
|
39
|
+
*
|
|
40
|
+
* Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
|
|
41
|
+
* reference implementation).
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolvePromptContext(client: unknown, sessionId: string): Promise<ResolvedPromptContext | null>;
|
|
44
|
+
export interface LastAssistantModel {
|
|
45
|
+
providerID: string;
|
|
46
|
+
modelID: string;
|
|
47
|
+
variant?: string;
|
|
48
|
+
}
|
|
49
|
+
/** @deprecated Use {@link resolvePromptContext} which also returns agent. */
|
|
50
|
+
export declare function getLastAssistantModel(client: unknown, sessionId: string): Promise<LastAssistantModel | null>;
|
|
51
|
+
//# sourceMappingURL=last-assistant-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"last-assistant-model.d.ts","sourceRoot":"","sources":["../../src/shared/last-assistant-model.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA0ED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAwCvC;AAID,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAQpC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the project directory the session was created with. Returns
|
|
3
|
+
* `null` when the lookup is unavailable or fails — callers should fall
|
|
4
|
+
* back to the runtime's directory in that case.
|
|
5
|
+
*
|
|
6
|
+
* This function is best-effort: any error (missing `client.session.get`,
|
|
7
|
+
* network failure, malformed response) is logged and recorded as a
|
|
8
|
+
* negative cache entry so we don't retry on every tool call within the
|
|
9
|
+
* same session.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getSessionDirectory(client: unknown, sessionId: string, fallbackDirectory: string): Promise<string | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Synchronous cache probe. Returns the resolved directory for a session if we
|
|
14
|
+
* already looked it up; otherwise `undefined` so the caller falls through to
|
|
15
|
+
* its synchronous fallback (typically `runtime.directory`).
|
|
16
|
+
*
|
|
17
|
+
* This is the hot path: `bridgeFor()` runs on every tool call and must not
|
|
18
|
+
* block on an SDK round-trip. The async {@link warmSessionDirectory} should
|
|
19
|
+
* be called eagerly (without await) at the start of each tool call to keep
|
|
20
|
+
* the cache filled, so by the time a second call from the same session
|
|
21
|
+
* arrives, this probe returns the correct directory.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getSessionDirectoryCached(sessionId: string | undefined): string | null | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Fire-and-forget cache warmup. Safe to call from synchronous code; failures
|
|
26
|
+
* are logged but not propagated. Subsequent calls to {@link getSessionDirectoryCached}
|
|
27
|
+
* will return the resolved directory once the lookup completes.
|
|
28
|
+
*/
|
|
29
|
+
export declare function warmSessionDirectory(client: unknown, sessionId: string | undefined, fallbackDirectory: string): void;
|
|
30
|
+
/** Test-only: clear the cache between unit tests. */
|
|
31
|
+
export declare function _resetSessionDirectoryCacheForTest(): void;
|
|
32
|
+
//# sourceMappingURL=session-directory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-directory.d.ts","sourceRoot":"","sources":["../../src/shared/session-directory.ts"],"names":[],"mappings":"AA8CA;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0CxB;AAWD;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,GAAG,IAAI,GAAG,SAAS,CAK3B;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,iBAAiB,EAAE,MAAM,GACxB,IAAI,CAIN;AAED,qDAAqD;AACrD,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD"}
|
package/dist/tools/_shared.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* `bridge.send()` multiple times with shared state) should use `bridgeFor()`
|
|
16
16
|
* and still pass `session_id` explicitly.
|
|
17
17
|
*/
|
|
18
|
-
import type { BinaryBridge, BridgeRequestOptions } from "
|
|
18
|
+
import type { BinaryBridge, BridgeRequestOptions } from "@cortexkit/aft-bridge";
|
|
19
19
|
import type { PluginContext } from "../types.js";
|
|
20
20
|
/**
|
|
21
21
|
* Per-command timeout overrides (milliseconds).
|
|
@@ -49,14 +49,25 @@ export interface ToolRuntime {
|
|
|
49
49
|
* Prefers `worktree` because that stays stable across OpenCode sessions in
|
|
50
50
|
* the same project; falls back to `directory` when unavailable (standalone
|
|
51
51
|
* CLI use, older hosts). Normalizes trailing slashes and resolves symlinks
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* so `/repo` and `/repo/` and `/Users/.../repo -> /Volumes/...` collapse to
|
|
53
|
+
* the same key.
|
|
54
|
+
*
|
|
55
|
+
* NOTE: When the runtime carries a `sessionID` and we have a cached
|
|
56
|
+
* session-stored directory for it (see `shared/session-directory.ts`), the
|
|
57
|
+
* stored directory wins. This is the workaround for OpenCode's bug where
|
|
58
|
+
* `ctx.directory` is set to `process.cwd()` rather than the resumed
|
|
59
|
+
* session's actual project directory.
|
|
54
60
|
*/
|
|
55
61
|
export declare function projectRootFor(runtime: ToolRuntime): string;
|
|
56
62
|
/**
|
|
57
63
|
* Get the BinaryBridge for the runtime's project root.
|
|
58
64
|
*
|
|
59
65
|
* Prefer `callBridge()` unless you need to send multiple requests yourself.
|
|
66
|
+
*
|
|
67
|
+
* This is synchronous and uses only the cached session directory. If the
|
|
68
|
+
* cache is cold, it falls back to `runtime.directory` — `callBridge()`
|
|
69
|
+
* eagerly warms the cache before calling this so the cache is hot for
|
|
70
|
+
* subsequent calls in the same session.
|
|
60
71
|
*/
|
|
61
72
|
export declare function bridgeFor(ctx: PluginContext, runtime: ToolRuntime): BinaryBridge;
|
|
62
73
|
/**
|
|
@@ -66,9 +77,20 @@ export declare function bridgeFor(ctx: PluginContext, runtime: ToolRuntime): Bin
|
|
|
66
77
|
* the right bridge (project-keyed), attaches the session namespace from
|
|
67
78
|
* `context.sessionID`, and returns whatever the binary responds.
|
|
68
79
|
*
|
|
80
|
+
* Before routing, it ensures the session-directory cache is warm so the
|
|
81
|
+
* very first tool call on a resumed-from-wrong-cwd session still reaches
|
|
82
|
+
* the correct project bridge. Subsequent calls hit the cache synchronously.
|
|
83
|
+
*
|
|
69
84
|
* The Rust side falls back to a shared default namespace when `session_id`
|
|
70
85
|
* is absent (see `RawRequest::session()`), so hosts that don't expose a
|
|
71
86
|
* session identifier still work — they just share undo/checkpoint state.
|
|
72
87
|
*/
|
|
73
|
-
export declare function callBridge(ctx: PluginContext, runtime: ToolRuntime, command: string, params?: Record<string, unknown>, options?: BridgeRequestOptions):
|
|
88
|
+
export declare function callBridge(ctx: PluginContext, runtime: ToolRuntime, command: string, params?: Record<string, unknown>, options?: BridgeRequestOptions): Promise<Record<string, unknown>>;
|
|
89
|
+
/**
|
|
90
|
+
* Eagerly warm the session-directory cache for a runtime. Safe to call from
|
|
91
|
+
* synchronous code — the lookup runs in the background and failures are
|
|
92
|
+
* logged. Useful in plugin lifecycle hooks (`chat.message`, etc.) where we
|
|
93
|
+
* want the cache filled before any tool call arrives.
|
|
94
|
+
*/
|
|
95
|
+
export declare function warmSessionDirectoryFromRuntime(ctx: PluginContext, runtime: ToolRuntime): void;
|
|
74
96
|
//# sourceMappingURL=_shared.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;
|
|
1
|
+
{"version":3,"file":"_shared.d.ts","sourceRoot":"","sources":["../../src/tools/_shared.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAOhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAQlE,CAAC;AAEF,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAW3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,YAAY,CAEhF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA0BlC;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAAC,GAAG,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,CAE9F"}
|
package/dist/tools/bash.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../src/tools/bash.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAIvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAsDjD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA6JjE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAqCvE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAmBrE"}
|
package/dist/tools/hoisted.d.ts
CHANGED
|
@@ -18,7 +18,15 @@ export declare const _buildUnifiedDiffForTest: (fp: string, before: string, afte
|
|
|
18
18
|
export declare function createReadTool(ctx: PluginContext): ToolDefinition;
|
|
19
19
|
/**
|
|
20
20
|
* Returns hoisted tools keyed by opencode's built-in names.
|
|
21
|
-
* Overrides: read, write, edit, apply_patch.
|
|
21
|
+
* Overrides: read, write, edit, apply_patch (always when hoisting is on).
|
|
22
|
+
*
|
|
23
|
+
* Bash hoisting is opt-in: `bash`, `bash_status`, and `bash_kill` are only
|
|
24
|
+
* registered when at least one `experimental.bash.*` flag is enabled
|
|
25
|
+
* (rewrite, compress, or background). When all flags are off, opencode's
|
|
26
|
+
* native bash stays in place — users without bash experimentals get zero
|
|
27
|
+
* AFT code in their bash path. `bash_status` and `bash_kill` register
|
|
28
|
+
* specifically when `experimental.bash.background` is true, since those
|
|
29
|
+
* tools only have meaning for background tasks.
|
|
22
30
|
*/
|
|
23
31
|
export declare function hoistedTools(ctx: PluginContext): Record<string, ToolDefinition>;
|
|
24
32
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;
|
|
1
|
+
{"version":3,"file":"hoisted.d.ts","sourceRoot":"","sources":["../../src/tools/hoisted.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAkEjD,wEAAwE;AACxE,eAAO,MAAM,wBAAwB,GAAI,IAAI,MAAM,EAAE,QAAQ,MAAM,EAAE,OAAO,MAAM,KAAG,MAChD,CAAC;AAqRtC;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CAgJjE;AAqgCD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA0B/E;AAMD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAmEnF"}
|
package/dist/tools/reading.d.ts
CHANGED
|
@@ -15,7 +15,13 @@ interface ZoomBatchResult {
|
|
|
15
15
|
* Tool definitions for code reading commands: outline + zoom.
|
|
16
16
|
*/
|
|
17
17
|
export declare function readingTools(ctx: PluginContext): Record<string, ToolDefinition>;
|
|
18
|
-
/**
|
|
19
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Format multi-symbol zoom results as plain text. Successful entries use
|
|
20
|
+
* `formatZoomText` (line-numbered, no JSON escapes); failures render as
|
|
21
|
+
* `Symbol "name" not found: <reason>`. Sections are blank-line separated.
|
|
22
|
+
*
|
|
23
|
+
* Exported for regression tests.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatZoomBatchResult(targetLabel: string, symbols: string[], responses: Record<string, unknown>[]): ZoomBatchResult;
|
|
20
26
|
export {};
|
|
21
27
|
//# sourceMappingURL=reading.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"reading.d.ts","sourceRoot":"","sources":["../../src/tools/reading.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAKjD,UAAU,qBAAqB;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,eAAe;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CA2J/E;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,EACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACnC,eAAe,CAyBjB"}
|
package/dist/tui.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// package.json
|
|
3
3
|
var package_default = {
|
|
4
4
|
name: "@cortexkit/aft-opencode",
|
|
5
|
-
version: "0.
|
|
5
|
+
version: "0.19.1",
|
|
6
6
|
type: "module",
|
|
7
7
|
description: "OpenCode plugin for Agent File Tools (AFT) \u2014 tree-sitter and lsp powered code analysis",
|
|
8
8
|
main: "dist/index.js",
|
|
@@ -30,6 +30,7 @@ var package_default = {
|
|
|
30
30
|
},
|
|
31
31
|
dependencies: {
|
|
32
32
|
"@clack/prompts": "^1.2.0",
|
|
33
|
+
"@cortexkit/aft-bridge": "0.19.1",
|
|
33
34
|
"@opencode-ai/plugin": "^1.2.26",
|
|
34
35
|
"@opencode-ai/sdk": "^1.2.26",
|
|
35
36
|
"comment-json": "^4.6.2",
|
|
@@ -37,11 +38,11 @@ var package_default = {
|
|
|
37
38
|
zod: "^4.1.8"
|
|
38
39
|
},
|
|
39
40
|
optionalDependencies: {
|
|
40
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
41
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
42
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
43
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
44
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
41
|
+
"@cortexkit/aft-darwin-arm64": "0.19.1",
|
|
42
|
+
"@cortexkit/aft-darwin-x64": "0.19.1",
|
|
43
|
+
"@cortexkit/aft-linux-arm64": "0.19.1",
|
|
44
|
+
"@cortexkit/aft-linux-x64": "0.19.1",
|
|
45
|
+
"@cortexkit/aft-win32-x64": "0.19.1"
|
|
45
46
|
},
|
|
46
47
|
devDependencies: {
|
|
47
48
|
"@types/node": "^22.0.0",
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { BridgePool } from "@cortexkit/aft-bridge";
|
|
1
2
|
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
3
|
import type { AftConfig } from "./config.js";
|
|
3
|
-
import type { BridgePool } from "./pool.js";
|
|
4
4
|
interface ShellEnvPluginHost {
|
|
5
5
|
trigger?: (name: "shell.env", context: {
|
|
6
6
|
cwd: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,UAAU,kBAAkB;IAC1B,OAAO,CAAC,EAAE,CACR,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAC7D,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,KACnC,OAAO,CAAC;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;CACnF;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,WAAW,EAAE,SAAS,GAAG,aAAa,GAAG,KAAK,CAAC;IAC/C,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,eAAe,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"workflow-hints.d.ts","sourceRoot":"","sources":["../src/workflow-hints.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,WAAW,EAAE,SAAS,GAAG,aAAa,GAAG,KAAK,CAAC;IAC/C,4EAA4E;IAC5E,aAAa,EAAE,OAAO,CAAC;IACvB,mEAAmE;IACnE,eAAe,EAAE,OAAO,CAAC;IACzB,wEAAwE;IACxE,qBAAqB,EAAE,OAAO,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CA2EzE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAQjG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/aft-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Agent File Tools (AFT) — tree-sitter and lsp powered code analysis",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@clack/prompts": "^1.2.0",
|
|
31
|
+
"@cortexkit/aft-bridge": "0.19.1",
|
|
31
32
|
"@opencode-ai/plugin": "^1.2.26",
|
|
32
33
|
"@opencode-ai/sdk": "^1.2.26",
|
|
33
34
|
"comment-json": "^4.6.2",
|
|
@@ -35,11 +36,11 @@
|
|
|
35
36
|
"zod": "^4.1.8"
|
|
36
37
|
},
|
|
37
38
|
"optionalDependencies": {
|
|
38
|
-
"@cortexkit/aft-darwin-arm64": "0.
|
|
39
|
-
"@cortexkit/aft-darwin-x64": "0.
|
|
40
|
-
"@cortexkit/aft-linux-arm64": "0.
|
|
41
|
-
"@cortexkit/aft-linux-x64": "0.
|
|
42
|
-
"@cortexkit/aft-win32-x64": "0.
|
|
39
|
+
"@cortexkit/aft-darwin-arm64": "0.19.1",
|
|
40
|
+
"@cortexkit/aft-darwin-x64": "0.19.1",
|
|
41
|
+
"@cortexkit/aft-linux-arm64": "0.19.1",
|
|
42
|
+
"@cortexkit/aft-linux-x64": "0.19.1",
|
|
43
|
+
"@cortexkit/aft-win32-x64": "0.19.1"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@types/node": "^22.0.0",
|
package/src/logger.ts
CHANGED
|
@@ -102,3 +102,22 @@ export function sessionError(sessionId: string | undefined, message: string, dat
|
|
|
102
102
|
export function getLogFilePath(): string {
|
|
103
103
|
return logFile;
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Adapter that exposes this logger as a {@link import("@cortexkit/aft-bridge").Logger}
|
|
108
|
+
* for the shared bridge package. The bridge package never knows about this log
|
|
109
|
+
* file or the `[aft-plugin]` tag — it just calls `log/warn/error` and we map
|
|
110
|
+
* its `LogMeta.sessionId` into our internal session-prefix shape.
|
|
111
|
+
*/
|
|
112
|
+
export const bridgeLogger = {
|
|
113
|
+
log(message: string, meta?: { sessionId?: string }) {
|
|
114
|
+
sessionLog(meta?.sessionId, message);
|
|
115
|
+
},
|
|
116
|
+
warn(message: string, meta?: { sessionId?: string }) {
|
|
117
|
+
sessionWarn(meta?.sessionId, message);
|
|
118
|
+
},
|
|
119
|
+
error(message: string, meta?: { sessionId?: string }) {
|
|
120
|
+
sessionError(meta?.sessionId, message);
|
|
121
|
+
},
|
|
122
|
+
getLogFilePath: () => logFile,
|
|
123
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache-busting fix for prompt prefix cache eviction.
|
|
3
|
+
*
|
|
4
|
+
* PROBLEM: Every PromptInput we send to OpenCode (notifications, idle
|
|
5
|
+
* wakeups, ignored messages) creates a new user message. OpenCode's
|
|
6
|
+
* `createUserMessage` resolves variant relative to the chosen agent. If
|
|
7
|
+
* we don't pass model/variant, defaults take over and bust the provider
|
|
8
|
+
* prefix cache that the previous assistant turn warmed.
|
|
9
|
+
*
|
|
10
|
+
* APPROACH: Mirror what `opencode-xtra` does in production. Read recent
|
|
11
|
+
* messages from `client.session.messages()`, prefer the most recent
|
|
12
|
+
* assistant message, fall back to any role, MERGE across messages so
|
|
13
|
+
* partial fields (e.g. agent set on user message but not assistant) are
|
|
14
|
+
* filled in, and read BOTH the flat shape (`info.providerID`) used by
|
|
15
|
+
* AssistantMessage and the nested shape (`info.model.providerID`) used
|
|
16
|
+
* by UserMessage.
|
|
17
|
+
*
|
|
18
|
+
* IMPORTANT: This context is only meaningful for callers that DO trigger
|
|
19
|
+
* LLM inference (e.g. background-bash idle wakes with `noReply: false`).
|
|
20
|
+
* Callers using `noReply: true` (one-off ignored messages, warnings,
|
|
21
|
+
* announcements) never trigger inference, so they don't need model or
|
|
22
|
+
* variant — the model/variant pass-through there is unnecessary AND has
|
|
23
|
+
* been observed to crash OpenCode under some configurations. Limit
|
|
24
|
+
* model/variant pass-through to wake-style calls.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export interface ResolvedPromptContext {
|
|
28
|
+
agent?: string;
|
|
29
|
+
model?: { providerID: string; modelID: string };
|
|
30
|
+
variant?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface RawInfo {
|
|
34
|
+
role?: string;
|
|
35
|
+
agent?: string;
|
|
36
|
+
variant?: string;
|
|
37
|
+
providerID?: string;
|
|
38
|
+
modelID?: string;
|
|
39
|
+
model?: { providerID?: string; modelID?: string; variant?: string };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
43
|
+
return typeof value === "object" && value !== null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function extractMessages(response: unknown): unknown[] {
|
|
47
|
+
if (Array.isArray(response)) return response;
|
|
48
|
+
if (isRecord(response) && Array.isArray(response.data)) return response.data;
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getRole(message: unknown): string | undefined {
|
|
53
|
+
if (!isRecord(message) || !isRecord(message.info)) return undefined;
|
|
54
|
+
return typeof message.info.role === "string" ? message.info.role : undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractFromMessage(message: unknown): ResolvedPromptContext | null {
|
|
58
|
+
if (!isRecord(message) || !isRecord(message.info)) return null;
|
|
59
|
+
const info = message.info as RawInfo;
|
|
60
|
+
const modelInfo = isRecord(info.model) ? info.model : undefined;
|
|
61
|
+
|
|
62
|
+
const agent = typeof info.agent === "string" ? info.agent : undefined;
|
|
63
|
+
const providerID =
|
|
64
|
+
typeof modelInfo?.providerID === "string"
|
|
65
|
+
? modelInfo.providerID
|
|
66
|
+
: typeof info.providerID === "string"
|
|
67
|
+
? info.providerID
|
|
68
|
+
: undefined;
|
|
69
|
+
const modelID =
|
|
70
|
+
typeof modelInfo?.modelID === "string"
|
|
71
|
+
? modelInfo.modelID
|
|
72
|
+
: typeof info.modelID === "string"
|
|
73
|
+
? info.modelID
|
|
74
|
+
: undefined;
|
|
75
|
+
const variant =
|
|
76
|
+
typeof modelInfo?.variant === "string"
|
|
77
|
+
? modelInfo.variant
|
|
78
|
+
: typeof info.variant === "string"
|
|
79
|
+
? info.variant
|
|
80
|
+
: undefined;
|
|
81
|
+
|
|
82
|
+
if (!agent && (!providerID || !modelID) && !variant) return null;
|
|
83
|
+
const out: ResolvedPromptContext = {};
|
|
84
|
+
if (agent) out.agent = agent;
|
|
85
|
+
if (providerID && modelID) out.model = { providerID, modelID };
|
|
86
|
+
if (variant) out.variant = variant;
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function mergeContexts(
|
|
91
|
+
base: ResolvedPromptContext,
|
|
92
|
+
patch: ResolvedPromptContext,
|
|
93
|
+
): ResolvedPromptContext {
|
|
94
|
+
return {
|
|
95
|
+
agent: base.agent ?? patch.agent,
|
|
96
|
+
model: base.model ?? patch.model,
|
|
97
|
+
variant: base.variant ?? patch.variant,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isComplete(ctx: ResolvedPromptContext): boolean {
|
|
102
|
+
return Boolean(ctx.agent && ctx.model && ctx.variant);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Read recent messages from the OpenCode session and resolve the most
|
|
107
|
+
* recent assistant prompt context. Falls back to user messages if no
|
|
108
|
+
* assistant has the field. Merges across messages so partial fields are
|
|
109
|
+
* filled in. Returns null if no usable context is found.
|
|
110
|
+
*
|
|
111
|
+
* Mirrors `resolveSessionPromptParams` in `opencode-xtra` (the working
|
|
112
|
+
* reference implementation).
|
|
113
|
+
*/
|
|
114
|
+
export async function resolvePromptContext(
|
|
115
|
+
client: unknown,
|
|
116
|
+
sessionId: string,
|
|
117
|
+
): Promise<ResolvedPromptContext | null> {
|
|
118
|
+
if (!client || !sessionId) return null;
|
|
119
|
+
const c = client as {
|
|
120
|
+
session?: {
|
|
121
|
+
messages?: (input: { path: { id: string } }) => Promise<{ data?: unknown[] } | unknown[]>;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
if (typeof c.session?.messages !== "function") return null;
|
|
125
|
+
|
|
126
|
+
let messages: unknown[] = [];
|
|
127
|
+
try {
|
|
128
|
+
const response = await c.session.messages({ path: { id: sessionId } });
|
|
129
|
+
messages = extractMessages(response);
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
if (messages.length === 0) return null;
|
|
134
|
+
|
|
135
|
+
// Pass 1: prefer the most recent assistant, merge older assistants to
|
|
136
|
+
// fill missing fields.
|
|
137
|
+
let result: ResolvedPromptContext = {};
|
|
138
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
139
|
+
if (getRole(messages[i]) !== "assistant") continue;
|
|
140
|
+
const ctx = extractFromMessage(messages[i]);
|
|
141
|
+
if (!ctx) continue;
|
|
142
|
+
result = mergeContexts(result, ctx);
|
|
143
|
+
if (isComplete(result)) return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Pass 2: fall back to any role (covers user messages, which carry
|
|
147
|
+
// model nested under `info.model`).
|
|
148
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
149
|
+
const ctx = extractFromMessage(messages[i]);
|
|
150
|
+
if (!ctx) continue;
|
|
151
|
+
result = mergeContexts(result, ctx);
|
|
152
|
+
if (isComplete(result)) return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!result.agent && !result.model && !result.variant) return null;
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- Compatibility shim for any older caller still using this name ---
|
|
160
|
+
|
|
161
|
+
export interface LastAssistantModel {
|
|
162
|
+
providerID: string;
|
|
163
|
+
modelID: string;
|
|
164
|
+
variant?: string;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** @deprecated Use {@link resolvePromptContext} which also returns agent. */
|
|
168
|
+
export async function getLastAssistantModel(
|
|
169
|
+
client: unknown,
|
|
170
|
+
sessionId: string,
|
|
171
|
+
): Promise<LastAssistantModel | null> {
|
|
172
|
+
const ctx = await resolvePromptContext(client, sessionId);
|
|
173
|
+
if (!ctx?.model) return null;
|
|
174
|
+
return {
|
|
175
|
+
providerID: ctx.model.providerID,
|
|
176
|
+
modelID: ctx.model.modelID,
|
|
177
|
+
...(ctx.variant ? { variant: ctx.variant } : {}),
|
|
178
|
+
};
|
|
179
|
+
}
|