@cortexkit/opencode-magic-context 0.13.2 → 0.14.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/README.md +23 -11
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli.js +59 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/schema/magic-context.d.ts +61 -22
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
- package/dist/features/magic-context/git-commits/git-log-reader.d.ts +57 -0
- package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/index.d.ts +7 -0
- package/dist/features/magic-context/git-commits/index.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/indexer.d.ts +44 -0
- package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts +32 -0
- package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +18 -0
- package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -0
- package/dist/features/magic-context/git-commits/storage-git-commits.d.ts +47 -0
- package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -0
- package/dist/features/magic-context/memory/embedding-local.d.ts +2 -2
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-openai.d.ts +25 -2
- package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-provider.d.ts +8 -2
- package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts +2 -2
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/overflow-detection.d.ts +51 -0
- package/dist/features/magic-context/overflow-detection.d.ts.map +1 -0
- package/dist/features/magic-context/search.d.ts +40 -9
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +40 -0
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +1 -1
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-hint.d.ts +34 -0
- package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts +51 -0
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -0
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts +29 -0
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-types.d.ts +9 -0
- package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
- package/dist/hooks/magic-context/drop-stale-reduce-calls.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-payloads.d.ts +15 -1
- package/dist/hooks/magic-context/event-payloads.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +20 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts +5 -2
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +14 -6
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +12 -0
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/nudger.d.ts +0 -3
- package/dist/hooks/magic-context/nudger.d.ts.map +1 -1
- package/dist/hooks/magic-context/reasoning-capability.d.ts +23 -0
- package/dist/hooks/magic-context/reasoning-capability.d.ts.map +1 -0
- package/dist/hooks/magic-context/sentinel.d.ts +53 -0
- package/dist/hooks/magic-context/sentinel.d.ts.map +1 -0
- package/dist/hooks/magic-context/strip-content.d.ts +66 -16
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-structural-noise.d.ts +14 -0
- package/dist/hooks/magic-context/strip-structural-noise.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +18 -5
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts +2 -0
- package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +22 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +12 -0
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2288 -738
- package/dist/plugin/dream-timer.d.ts +24 -7
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +4 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/tool-registry.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts +20 -6
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/tools/ctx-note/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/constants.d.ts +1 -1
- package/dist/tools/ctx-search/constants.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/types.d.ts +8 -0
- package/dist/tools/ctx-search/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/shared/models-dev-cache.test.ts +57 -6
- package/src/shared/models-dev-cache.ts +119 -38
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { DreamerConfig, EmbeddingConfig } from "../config/schema/magic-context";
|
|
2
2
|
import type { PluginContext } from "./types";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* The timer is unref'd so it doesn't prevent the process from exiting.
|
|
4
|
+
* Per-project work registered with the timer. The timer is a process-wide
|
|
5
|
+
* singleton, but Desktop OpenCode can load the same plugin once per project
|
|
6
|
+
* within one process — every load needs its directory's git commits indexed,
|
|
7
|
+
* its dream schedule checked, and its experimental config respected.
|
|
9
8
|
*/
|
|
10
|
-
|
|
9
|
+
interface ProjectRegistration {
|
|
11
10
|
directory: string;
|
|
12
11
|
client: PluginContext["client"];
|
|
13
12
|
dreamerConfig?: DreamerConfig;
|
|
@@ -22,5 +21,23 @@ export declare function startDreamScheduleTimer(args: {
|
|
|
22
21
|
token_budget: number;
|
|
23
22
|
min_reads: number;
|
|
24
23
|
};
|
|
25
|
-
|
|
24
|
+
gitCommitIndexing?: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
since_days: number;
|
|
27
|
+
max_commits: number;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register the calling project with the process-wide dream + maintenance
|
|
32
|
+
* timer. The timer itself is a singleton (we only need one setInterval per
|
|
33
|
+
* process), but every registered project gets its per-directory work — git
|
|
34
|
+
* commit indexing, dream schedule check, dream queue processing — on each
|
|
35
|
+
* tick. The first registration also kicks off an immediate startup tick so
|
|
36
|
+
* fresh installs and restarts don't wait 15 minutes for first-time indexing.
|
|
37
|
+
*
|
|
38
|
+
* Returns a cleanup that removes this project's registration. The timer
|
|
39
|
+
* itself stops only when the last project unregisters.
|
|
40
|
+
*/
|
|
41
|
+
export declare function startDreamScheduleTimer(args: ProjectRegistration): (() => void) | undefined;
|
|
42
|
+
export {};
|
|
26
43
|
//# sourceMappingURL=dream-timer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dream-timer.d.ts","sourceRoot":"","sources":["../../src/plugin/dream-timer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"dream-timer.d.ts","sourceRoot":"","sources":["../../src/plugin/dream-timer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAUrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C;;;;;GAKG;AACH,UAAU,mBAAmB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,OAAO,CAAC;IACvB,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,uBAAuB,CAAC,EAAE;QACtB,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,iBAAiB,CAAC,EAAE;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACvB,CAAC;CACL;AAQD;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,mBAAmB,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAuD3F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAU7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;
|
|
1
|
+
{"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAU7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;qBAmD4gD,CAAC;;;;;;;;;;;;qBAbz+C,CAAC;mBAAyB,CAAC;iBAChD,CAAX;iBAAuB,CAAC;0BAAc,CAAC;uBAAiB,CAAC;;;;;;0BAYokmB,CAAC;;;;;;EADlomB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/plugin/tool-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAqB1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;CAC1C,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/plugin/tool-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAqB1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;CAC1C,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAsFjC"}
|
|
@@ -14,12 +14,20 @@
|
|
|
14
14
|
* Used during cold starts before the API cache warms up and in any
|
|
15
15
|
* code path that cannot reach the SDK client.
|
|
16
16
|
*
|
|
17
|
-
* The public `getModelsDevContextLimit()`
|
|
18
|
-
*
|
|
19
|
-
* the
|
|
17
|
+
* The public getters (`getModelsDevContextLimit()` and
|
|
18
|
+
* `getModelsDevInterleavedField()`) are synchronous: they check the API cache
|
|
19
|
+
* first, then the file cache. The plugin warms and refreshes the API cache
|
|
20
|
+
* from `src/index.ts` at startup and on a timer.
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
interface OpencodeClientLike {
|
|
23
|
+
config: {
|
|
24
|
+
providers: () => Promise<{
|
|
25
|
+
data?: {
|
|
26
|
+
providers?: unknown;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
23
31
|
/**
|
|
24
32
|
* Asynchronously refresh the API-layer cache from OpenCode's SDK.
|
|
25
33
|
*
|
|
@@ -31,7 +39,7 @@ type OpencodeClient = ReturnType<typeof createOpencodeClient>;
|
|
|
31
39
|
*
|
|
32
40
|
* Safe to call concurrently; only overwrites the cache on success.
|
|
33
41
|
*/
|
|
34
|
-
export declare function refreshModelLimitsFromApi(client:
|
|
42
|
+
export declare function refreshModelLimitsFromApi(client: OpencodeClientLike): Promise<void>;
|
|
35
43
|
/**
|
|
36
44
|
* Returns the context limit for a provider/model.
|
|
37
45
|
*
|
|
@@ -44,6 +52,12 @@ export declare function refreshModelLimitsFromApi(client: OpencodeClient): Promi
|
|
|
44
52
|
* Returns `undefined` if neither layer knows the model.
|
|
45
53
|
*/
|
|
46
54
|
export declare function getModelsDevContextLimit(providerID: string, modelID: string): number | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Returns the provider-specific interleaved reasoning field when the model
|
|
57
|
+
* requires one (for example `reasoning_content` for Moonshot/Kimi style
|
|
58
|
+
* providers). Undefined means the cache has no such capability recorded.
|
|
59
|
+
*/
|
|
60
|
+
export declare function getModelsDevInterleavedField(providerID: string, modelID: string): string | undefined;
|
|
47
61
|
/** Clear in-memory caches (for testing). */
|
|
48
62
|
export declare function clearModelsDevCache(): void;
|
|
49
63
|
/** Inspection helpers (for logging / debugging). */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAQH,UAAU,kBAAkB;IACxB,MAAM,EAAE;QACJ,SAAS,EAAE,MAAM,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,SAAS,CAAC,EAAE,OAAO,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAChE,CAAC;CACL;AA2ND;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDzF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAchG;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAChB,MAAM,GAAG,SAAS,CAkBpB;AAED,4CAA4C;AAC5C,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACrB,CAOA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAehE,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAyND,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAIxF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const CTX_SEARCH_TOOL_NAME = "ctx_search";
|
|
2
|
-
export declare const CTX_SEARCH_DESCRIPTION
|
|
2
|
+
export declare const CTX_SEARCH_DESCRIPTION: string;
|
|
3
3
|
export declare const DEFAULT_CTX_SEARCH_LIMIT = 10;
|
|
4
4
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,sBAAsB,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,sBAAsB,QAkBvB,CAAC;AACb,eAAO,MAAM,wBAAwB,KAAK,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAShE,OAAO,KAAK,EAAkC,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAyIjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAI5F"}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
|
+
/** Sources the agent can narrow ctx_search to. Facts are intentionally NOT a
|
|
3
|
+
* source — they're always rendered in <session-history> in message[0], so
|
|
4
|
+
* searching them returns content already visible in context. */
|
|
5
|
+
export type CtxSearchSource = "memory" | "message" | "git_commit";
|
|
2
6
|
export interface CtxSearchArgs {
|
|
3
7
|
query: string;
|
|
4
8
|
limit?: number;
|
|
9
|
+
/** Restrict search to specific sources. Omit to search all. */
|
|
10
|
+
sources?: CtxSearchSource[];
|
|
5
11
|
}
|
|
6
12
|
export interface CtxSearchToolDeps {
|
|
7
13
|
db: Database;
|
|
8
14
|
projectPath: string;
|
|
9
15
|
memoryEnabled: boolean;
|
|
10
16
|
embeddingEnabled: boolean;
|
|
17
|
+
/** When true, ctx_search surfaces indexed git commits as a 3rd source. */
|
|
18
|
+
gitCommitsEnabled?: boolean;
|
|
11
19
|
/** Override message reader for testing (avoids opening OpenCode DB in CI). */
|
|
12
20
|
readMessages?: (sessionId: string) => Array<{
|
|
13
21
|
ordinal: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;iEAEiE;AACjE,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAElE,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAC9B,EAAE,EAAE,QAAQ,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,8EAA8E;IAC9E,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,KAAK,CAAC;QACxC,OAAO,EAAE,MAAM,CAAC;QAChB,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,OAAO,EAAE,CAAC;KACpB,CAAC,CAAC;CACN"}
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
clearModelsDevCache,
|
|
7
7
|
getModelsDevCacheState,
|
|
8
8
|
getModelsDevContextLimit,
|
|
9
|
+
getModelsDevInterleavedField,
|
|
9
10
|
refreshModelLimitsFromApi,
|
|
10
11
|
} from "./models-dev-cache";
|
|
11
12
|
|
|
@@ -167,7 +168,6 @@ describe("models-dev-cache", () => {
|
|
|
167
168
|
}),
|
|
168
169
|
},
|
|
169
170
|
};
|
|
170
|
-
// @ts-expect-error mock narrow shape
|
|
171
171
|
await refreshModelLimitsFromApi(mockClient);
|
|
172
172
|
|
|
173
173
|
//#then
|
|
@@ -266,7 +266,6 @@ describe("models-dev-cache", () => {
|
|
|
266
266
|
}),
|
|
267
267
|
},
|
|
268
268
|
};
|
|
269
|
-
// @ts-expect-error mock narrow shape
|
|
270
269
|
await refreshModelLimitsFromApi(mockClient);
|
|
271
270
|
|
|
272
271
|
// API value wins.
|
|
@@ -277,23 +276,76 @@ describe("models-dev-cache", () => {
|
|
|
277
276
|
expect(state.apiCount).toBe(1);
|
|
278
277
|
});
|
|
279
278
|
|
|
279
|
+
test("reads interleaved reasoning field metadata from models.json", () => {
|
|
280
|
+
const opencodeDir = join(tempDir, "opencode");
|
|
281
|
+
mkdirSync(opencodeDir, { recursive: true });
|
|
282
|
+
writeFileSync(
|
|
283
|
+
join(opencodeDir, "models.json"),
|
|
284
|
+
JSON.stringify({
|
|
285
|
+
"opencode-go": {
|
|
286
|
+
models: {
|
|
287
|
+
"kimi-k2.6": {
|
|
288
|
+
limit: { context: 262144 },
|
|
289
|
+
interleaved: { field: "reasoning_content" },
|
|
290
|
+
},
|
|
291
|
+
"plain-model": {
|
|
292
|
+
limit: { context: 262144 },
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
}),
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
expect(getModelsDevInterleavedField("opencode-go", "kimi-k2.6")).toBe("reasoning_content");
|
|
300
|
+
expect(getModelsDevInterleavedField("opencode-go", "plain-model")).toBeUndefined();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("API cache exposes interleaved reasoning metadata", async () => {
|
|
304
|
+
const mockClient = {
|
|
305
|
+
config: {
|
|
306
|
+
providers: async () => ({
|
|
307
|
+
data: {
|
|
308
|
+
providers: [
|
|
309
|
+
{
|
|
310
|
+
id: "opencode-go",
|
|
311
|
+
models: {
|
|
312
|
+
"kimi-k2.6": {
|
|
313
|
+
limit: { context: 262144 },
|
|
314
|
+
capabilities: {
|
|
315
|
+
interleaved: { field: "reasoning_content" },
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
"plain-model": {
|
|
319
|
+
limit: { context: 262144 },
|
|
320
|
+
capabilities: { interleaved: false },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
await refreshModelLimitsFromApi(mockClient);
|
|
330
|
+
|
|
331
|
+
expect(getModelsDevInterleavedField("opencode-go", "kimi-k2.6")).toBe("reasoning_content");
|
|
332
|
+
expect(getModelsDevInterleavedField("opencode-go", "plain-model")).toBeUndefined();
|
|
333
|
+
});
|
|
334
|
+
|
|
280
335
|
test("refreshModelLimitsFromApi tolerates empty/malformed responses", async () => {
|
|
281
336
|
// Undefined data.
|
|
282
|
-
// @ts-expect-error mock narrow shape
|
|
283
337
|
await refreshModelLimitsFromApi({
|
|
284
338
|
config: { providers: async () => ({ data: undefined }) },
|
|
285
339
|
});
|
|
286
340
|
expect(getModelsDevCacheState().apiLoaded).toBe(false);
|
|
287
341
|
|
|
288
342
|
// Non-array providers.
|
|
289
|
-
// @ts-expect-error mock narrow shape
|
|
290
343
|
await refreshModelLimitsFromApi({
|
|
291
344
|
config: { providers: async () => ({ data: { providers: "not an array" } }) },
|
|
292
345
|
});
|
|
293
346
|
expect(getModelsDevCacheState().apiLoaded).toBe(false);
|
|
294
347
|
|
|
295
348
|
// Thrown error.
|
|
296
|
-
// @ts-expect-error mock narrow shape
|
|
297
349
|
await refreshModelLimitsFromApi({
|
|
298
350
|
config: {
|
|
299
351
|
providers: async () => {
|
|
@@ -330,7 +382,6 @@ describe("models-dev-cache", () => {
|
|
|
330
382
|
}),
|
|
331
383
|
},
|
|
332
384
|
};
|
|
333
|
-
// @ts-expect-error mock narrow shape
|
|
334
385
|
await refreshModelLimitsFromApi(mockClient);
|
|
335
386
|
|
|
336
387
|
// API-only key comes from API.
|
|
@@ -14,28 +14,39 @@
|
|
|
14
14
|
* Used during cold starts before the API cache warms up and in any
|
|
15
15
|
* code path that cannot reach the SDK client.
|
|
16
16
|
*
|
|
17
|
-
* The public `getModelsDevContextLimit()`
|
|
18
|
-
*
|
|
19
|
-
* the
|
|
17
|
+
* The public getters (`getModelsDevContextLimit()` and
|
|
18
|
+
* `getModelsDevInterleavedField()`) are synchronous: they check the API cache
|
|
19
|
+
* first, then the file cache. The plugin warms and refreshes the API cache
|
|
20
|
+
* from `src/index.ts` at startup and on a timer.
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
23
|
import { createHash } from "node:crypto";
|
|
23
24
|
import { existsSync, readFileSync } from "node:fs";
|
|
24
25
|
import { homedir, platform } from "node:os";
|
|
25
26
|
import { join } from "node:path";
|
|
26
|
-
import type { createOpencodeClient } from "@opencode-ai/sdk";
|
|
27
27
|
import { sessionLog } from "./logger";
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
interface OpencodeClientLike {
|
|
30
|
+
config: {
|
|
31
|
+
providers: () => Promise<{ data?: { providers?: unknown } }>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
30
34
|
|
|
31
35
|
const RELOAD_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes, matches OpenCode's TTL
|
|
32
36
|
|
|
37
|
+
interface CachedModelMetadata {
|
|
38
|
+
limit?: number;
|
|
39
|
+
interleavedField?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type InterleavedConfig = boolean | { field?: string } | undefined;
|
|
43
|
+
|
|
33
44
|
/** Populated async from OpenCode SDK. Primary source of truth when available. */
|
|
34
|
-
let apiCache: Map<string,
|
|
45
|
+
let apiCache: Map<string, CachedModelMetadata> | null = null;
|
|
35
46
|
let apiLoadedAt = 0;
|
|
36
47
|
|
|
37
48
|
/** Populated sync from disk as fallback. */
|
|
38
|
-
let fileCache: Map<string,
|
|
49
|
+
let fileCache: Map<string, CachedModelMetadata> | null = null;
|
|
39
50
|
let fileLastAttempt = 0;
|
|
40
51
|
|
|
41
52
|
function hashFast(input: string): string {
|
|
@@ -104,8 +115,57 @@ function resolveLimit(limit: { context?: number; input?: number } | undefined):
|
|
|
104
115
|
return undefined;
|
|
105
116
|
}
|
|
106
117
|
|
|
107
|
-
function
|
|
108
|
-
|
|
118
|
+
function resolveInterleavedField(interleaved: InterleavedConfig): string | undefined {
|
|
119
|
+
if (
|
|
120
|
+
interleaved &&
|
|
121
|
+
typeof interleaved === "object" &&
|
|
122
|
+
typeof interleaved.field === "string" &&
|
|
123
|
+
interleaved.field.length > 0
|
|
124
|
+
) {
|
|
125
|
+
return interleaved.field;
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function setCachedModelMetadata(
|
|
131
|
+
cache: Map<string, CachedModelMetadata>,
|
|
132
|
+
key: string,
|
|
133
|
+
model:
|
|
134
|
+
| {
|
|
135
|
+
limit?: { context?: number; input?: number };
|
|
136
|
+
capabilities?: { interleaved?: InterleavedConfig };
|
|
137
|
+
interleaved?: InterleavedConfig;
|
|
138
|
+
experimental?: { modes?: Record<string, unknown> };
|
|
139
|
+
}
|
|
140
|
+
| undefined,
|
|
141
|
+
): void {
|
|
142
|
+
const limit = resolveLimit(model?.limit);
|
|
143
|
+
const interleavedField =
|
|
144
|
+
resolveInterleavedField(model?.capabilities?.interleaved) ??
|
|
145
|
+
resolveInterleavedField(model?.interleaved);
|
|
146
|
+
|
|
147
|
+
if (limit === undefined && interleavedField === undefined) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const value: CachedModelMetadata = {};
|
|
152
|
+
if (limit !== undefined) value.limit = limit;
|
|
153
|
+
if (interleavedField !== undefined) value.interleavedField = interleavedField;
|
|
154
|
+
cache.set(key, value);
|
|
155
|
+
|
|
156
|
+
// OpenCode creates derived model IDs from experimental.modes
|
|
157
|
+
// e.g. gpt-5.4 + modes.fast → gpt-5.4-fast. These inherit the same
|
|
158
|
+
// context limit and interleaved-reasoning contract as the parent model.
|
|
159
|
+
const modes = model?.experimental?.modes;
|
|
160
|
+
if (modes && typeof modes === "object") {
|
|
161
|
+
for (const mode of Object.keys(modes)) {
|
|
162
|
+
cache.set(`${key}-${mode}`, value);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function loadModelsDevMetadataFromFile(): Map<string, CachedModelMetadata> {
|
|
168
|
+
const metadata = new Map<string, CachedModelMetadata>();
|
|
109
169
|
|
|
110
170
|
// 1. Read OpenCode's models.dev cache file (base layer).
|
|
111
171
|
const modelsJsonPath = getModelsJsonPath();
|
|
@@ -121,6 +181,8 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
|
|
|
121
181
|
string,
|
|
122
182
|
{
|
|
123
183
|
limit?: { context?: number; input?: number };
|
|
184
|
+
capabilities?: { interleaved?: InterleavedConfig };
|
|
185
|
+
interleaved?: InterleavedConfig;
|
|
124
186
|
experimental?: { modes?: Record<string, unknown> };
|
|
125
187
|
}
|
|
126
188
|
>;
|
|
@@ -130,18 +192,7 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
|
|
|
130
192
|
for (const [providerId, provider] of Object.entries(data)) {
|
|
131
193
|
if (!provider?.models || typeof provider.models !== "object") continue;
|
|
132
194
|
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
133
|
-
|
|
134
|
-
if (typeof effective === "number" && effective > 0) {
|
|
135
|
-
limits.set(`${providerId}/${modelId}`, effective);
|
|
136
|
-
// OpenCode creates derived model IDs from experimental.modes
|
|
137
|
-
// e.g. gpt-5.4 + modes.fast → gpt-5.4-fast (inherits parent limit).
|
|
138
|
-
const modes = model?.experimental?.modes;
|
|
139
|
-
if (modes && typeof modes === "object") {
|
|
140
|
-
for (const mode of Object.keys(modes)) {
|
|
141
|
-
limits.set(`${providerId}/${modelId}-${mode}`, effective);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
195
|
+
setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
|
|
145
196
|
}
|
|
146
197
|
}
|
|
147
198
|
}
|
|
@@ -177,10 +228,7 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
|
|
|
177
228
|
for (const [providerId, provider] of Object.entries(config.provider)) {
|
|
178
229
|
if (!provider?.models || typeof provider.models !== "object") continue;
|
|
179
230
|
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
180
|
-
|
|
181
|
-
if (typeof effective === "number" && effective > 0) {
|
|
182
|
-
limits.set(`${providerId}/${modelId}`, effective);
|
|
183
|
-
}
|
|
231
|
+
setCachedModelMetadata(metadata, `${providerId}/${modelId}`, model);
|
|
184
232
|
}
|
|
185
233
|
}
|
|
186
234
|
}
|
|
@@ -195,10 +243,10 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
|
|
|
195
243
|
|
|
196
244
|
sessionLog(
|
|
197
245
|
"global",
|
|
198
|
-
`models-dev-cache: file-layer loaded ${
|
|
246
|
+
`models-dev-cache: file-layer loaded ${metadata.size} model metadata entries (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`,
|
|
199
247
|
);
|
|
200
248
|
|
|
201
|
-
return
|
|
249
|
+
return metadata;
|
|
202
250
|
}
|
|
203
251
|
|
|
204
252
|
/**
|
|
@@ -212,7 +260,7 @@ function loadModelsDevLimitsFromFile(): Map<string, number> {
|
|
|
212
260
|
*
|
|
213
261
|
* Safe to call concurrently; only overwrites the cache on success.
|
|
214
262
|
*/
|
|
215
|
-
export async function refreshModelLimitsFromApi(client:
|
|
263
|
+
export async function refreshModelLimitsFromApi(client: OpencodeClientLike): Promise<void> {
|
|
216
264
|
try {
|
|
217
265
|
const result = await client.config.providers();
|
|
218
266
|
const data = (result as { data?: { providers?: Array<unknown> } }).data;
|
|
@@ -222,18 +270,23 @@ export async function refreshModelLimitsFromApi(client: OpencodeClient): Promise
|
|
|
222
270
|
return;
|
|
223
271
|
}
|
|
224
272
|
|
|
225
|
-
const map = new Map<string,
|
|
273
|
+
const map = new Map<string, CachedModelMetadata>();
|
|
226
274
|
for (const entry of providers) {
|
|
227
275
|
const p = entry as {
|
|
228
276
|
id?: string;
|
|
229
|
-
models?: Record<
|
|
277
|
+
models?: Record<
|
|
278
|
+
string,
|
|
279
|
+
{
|
|
280
|
+
limit?: { context?: number; input?: number };
|
|
281
|
+
capabilities?: { interleaved?: InterleavedConfig };
|
|
282
|
+
interleaved?: InterleavedConfig;
|
|
283
|
+
experimental?: { modes?: Record<string, unknown> };
|
|
284
|
+
}
|
|
285
|
+
>;
|
|
230
286
|
};
|
|
231
287
|
if (!p?.id || !p.models || typeof p.models !== "object") continue;
|
|
232
288
|
for (const [modelId, model] of Object.entries(p.models)) {
|
|
233
|
-
|
|
234
|
-
if (typeof effective === "number" && effective > 0) {
|
|
235
|
-
map.set(`${p.id}/${modelId}`, effective);
|
|
236
|
-
}
|
|
289
|
+
setCachedModelMetadata(map, `${p.id}/${modelId}`, model);
|
|
237
290
|
}
|
|
238
291
|
}
|
|
239
292
|
|
|
@@ -245,7 +298,7 @@ export async function refreshModelLimitsFromApi(client: OpencodeClient): Promise
|
|
|
245
298
|
if (previousSize === null || previousSize !== map.size) {
|
|
246
299
|
sessionLog(
|
|
247
300
|
"global",
|
|
248
|
-
`models-dev-cache: API layer loaded ${map.size} model
|
|
301
|
+
`models-dev-cache: API layer loaded ${map.size} model metadata entries${
|
|
249
302
|
previousSize !== null ? ` (was ${previousSize})` : ""
|
|
250
303
|
}`,
|
|
251
304
|
);
|
|
@@ -274,16 +327,44 @@ export function getModelsDevContextLimit(providerID: string, modelID: string): n
|
|
|
274
327
|
const key = `${providerID}/${modelID}`;
|
|
275
328
|
|
|
276
329
|
if (apiCache) {
|
|
277
|
-
const fromApi = apiCache.get(key);
|
|
330
|
+
const fromApi = apiCache.get(key)?.limit;
|
|
278
331
|
if (typeof fromApi === "number") return fromApi;
|
|
279
332
|
}
|
|
280
333
|
|
|
281
334
|
const now = Date.now();
|
|
282
335
|
if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
|
|
283
336
|
fileLastAttempt = now;
|
|
284
|
-
fileCache =
|
|
337
|
+
fileCache = loadModelsDevMetadataFromFile();
|
|
338
|
+
}
|
|
339
|
+
return fileCache.get(key)?.limit;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Returns the provider-specific interleaved reasoning field when the model
|
|
344
|
+
* requires one (for example `reasoning_content` for Moonshot/Kimi style
|
|
345
|
+
* providers). Undefined means the cache has no such capability recorded.
|
|
346
|
+
*/
|
|
347
|
+
export function getModelsDevInterleavedField(
|
|
348
|
+
providerID: string,
|
|
349
|
+
modelID: string,
|
|
350
|
+
): string | undefined {
|
|
351
|
+
const key = `${providerID}/${modelID}`;
|
|
352
|
+
|
|
353
|
+
if (apiCache) {
|
|
354
|
+
const fromApi = apiCache.get(key)?.interleavedField;
|
|
355
|
+
if (typeof fromApi === "string" && fromApi.length > 0) {
|
|
356
|
+
return fromApi;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const now = Date.now();
|
|
361
|
+
if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
|
|
362
|
+
fileLastAttempt = now;
|
|
363
|
+
fileCache = loadModelsDevMetadataFromFile();
|
|
285
364
|
}
|
|
286
|
-
|
|
365
|
+
|
|
366
|
+
const fromFile = fileCache.get(key)?.interleavedField;
|
|
367
|
+
return typeof fromFile === "string" && fromFile.length > 0 ? fromFile : undefined;
|
|
287
368
|
}
|
|
288
369
|
|
|
289
370
|
/** Clear in-memory caches (for testing). */
|