@cortexkit/opencode-magic-context 0.13.2 → 0.14.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 (82) hide show
  1. package/README.md +13 -7
  2. package/dist/cli/doctor.d.ts.map +1 -1
  3. package/dist/cli.js +59 -3
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/schema/magic-context.d.ts +61 -22
  6. package/dist/config/schema/magic-context.d.ts.map +1 -1
  7. package/dist/features/magic-context/compartment-storage.d.ts.map +1 -1
  8. package/dist/features/magic-context/git-commits/git-log-reader.d.ts +57 -0
  9. package/dist/features/magic-context/git-commits/git-log-reader.d.ts.map +1 -0
  10. package/dist/features/magic-context/git-commits/index.d.ts +7 -0
  11. package/dist/features/magic-context/git-commits/index.d.ts.map +1 -0
  12. package/dist/features/magic-context/git-commits/indexer.d.ts +44 -0
  13. package/dist/features/magic-context/git-commits/indexer.d.ts.map +1 -0
  14. package/dist/features/magic-context/git-commits/search-git-commits.d.ts +32 -0
  15. package/dist/features/magic-context/git-commits/search-git-commits.d.ts.map +1 -0
  16. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts +18 -0
  17. package/dist/features/magic-context/git-commits/storage-git-commit-embeddings.d.ts.map +1 -0
  18. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts +47 -0
  19. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -0
  20. package/dist/features/magic-context/memory/embedding-local.d.ts +2 -2
  21. package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
  22. package/dist/features/magic-context/memory/embedding-openai.d.ts +25 -2
  23. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  24. package/dist/features/magic-context/memory/embedding-provider.d.ts +8 -2
  25. package/dist/features/magic-context/memory/embedding-provider.d.ts.map +1 -1
  26. package/dist/features/magic-context/memory/embedding.d.ts +2 -2
  27. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  28. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  29. package/dist/features/magic-context/overflow-detection.d.ts +51 -0
  30. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -0
  31. package/dist/features/magic-context/search.d.ts +40 -9
  32. package/dist/features/magic-context/search.d.ts.map +1 -1
  33. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-meta-persisted.d.ts +40 -0
  35. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  36. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  37. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage.d.ts +1 -1
  39. package/dist/features/magic-context/storage.d.ts.map +1 -1
  40. package/dist/hooks/magic-context/auto-search-hint.d.ts +34 -0
  41. package/dist/hooks/magic-context/auto-search-hint.d.ts.map +1 -0
  42. package/dist/hooks/magic-context/auto-search-runner.d.ts +51 -0
  43. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -0
  44. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  45. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  46. package/dist/hooks/magic-context/event-payloads.d.ts +15 -1
  47. package/dist/hooks/magic-context/event-payloads.d.ts.map +1 -1
  48. package/dist/hooks/magic-context/event-resolvers.d.ts +20 -1
  49. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/hook-handlers.d.ts +5 -0
  51. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  52. package/dist/hooks/magic-context/hook.d.ts +14 -6
  53. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  54. package/dist/hooks/magic-context/inject-compartments.d.ts +12 -0
  55. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  56. package/dist/hooks/magic-context/note-nudger.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/reasoning-capability.d.ts +23 -0
  58. package/dist/hooks/magic-context/reasoning-capability.d.ts.map +1 -0
  59. package/dist/hooks/magic-context/strip-content.d.ts +3 -3
  60. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +22 -1
  62. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/transform.d.ts +12 -0
  64. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +2058 -569
  67. package/dist/plugin/dream-timer.d.ts +24 -7
  68. package/dist/plugin/dream-timer.d.ts.map +1 -1
  69. package/dist/plugin/hooks/create-session-hooks.d.ts +4 -0
  70. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  71. package/dist/plugin/tool-registry.d.ts.map +1 -1
  72. package/dist/shared/models-dev-cache.d.ts +20 -6
  73. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  74. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  75. package/dist/tools/ctx-search/constants.d.ts +1 -1
  76. package/dist/tools/ctx-search/constants.d.ts.map +1 -1
  77. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  78. package/dist/tools/ctx-search/types.d.ts +8 -0
  79. package/dist/tools/ctx-search/types.d.ts.map +1 -1
  80. package/package.json +1 -1
  81. package/src/shared/models-dev-cache.test.ts +57 -6
  82. 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
- * Start an independent timer that checks the dreamer schedule and processes
5
- * the dream queue. This runs regardless of user activity so overnight
6
- * dreaming triggers even when the user isn't chatting.
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
- export declare function startDreamScheduleTimer(args: {
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
- }): (() => void) | undefined;
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;AAKrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAS7C;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC1C,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;CACL,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAoF3B"}
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"}
@@ -26,6 +26,10 @@ export declare function createSessionHooks(args: {
26
26
  sessionID?: string;
27
27
  variant?: string;
28
28
  agent?: string;
29
+ model?: {
30
+ providerID?: string;
31
+ modelID?: string;
32
+ };
29
33
  }) => Promise<void>;
30
34
  event: (input: {
31
35
  event: {
@@ -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;;;;;;qBAiD4oB,CAAC;;;;;;;;;;;;qBAXzlB,CAAC;mBAAiB,CAAC;iBACxD,CAAX;;;;;0BAUy4hB,CAAC;;;;;;EAD94hB"}
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;;;;;;qBAmD0f,CAAC;;;;;;;;;;;;qBAV5e,CAAP;mBAAyB,CAAC;iBACnB,CAAN;iBAAuB,CAAC;0BAClB,CAAd;uBAAiB,CAAC;;;;;;0BAQ8ujB,CAAC;;;;;;EADlwjB"}
@@ -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,CAqFjC"}
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()` getter is synchronous: it checks
18
- * the API cache first, then the file cache. The plugin warms and refreshes
19
- * the API cache from `src/index.ts` at startup and on a timer.
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
- import type { createOpencodeClient } from "@opencode-ai/sdk";
22
- type OpencodeClient = ReturnType<typeof createOpencodeClient>;
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: OpencodeClient): Promise<void>;
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;;;;;;;;;;;;;;;;;;;GAmBG;AAMH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAG7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AA+K9D;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CrF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAchG;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
+ {"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;AAchE,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAgND,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAIxF"}
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 = "Search across project memories, session facts, and conversation history. Returns ranked results from all sources. Use message ordinals with ctx_expand to retrieve full conversation context around a result.";
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,kNACgL,CAAC;AACpN,eAAO,MAAM,wBAAwB,KAAK,CAAC"}
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;AAQhE,OAAO,KAAK,EAAiB,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAoFhE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAI5F"}
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;CAClB;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,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"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -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()` getter is synchronous: it checks
18
- * the API cache first, then the file cache. The plugin warms and refreshes
19
- * the API cache from `src/index.ts` at startup and on a timer.
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
- type OpencodeClient = ReturnType<typeof createOpencodeClient>;
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, number> | null = null;
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, number> | null = null;
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 loadModelsDevLimitsFromFile(): Map<string, number> {
108
- const limits = new Map<string, number>();
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
- const effective = resolveLimit(model?.limit);
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
- const effective = resolveLimit(model?.limit);
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 ${limits.size} model limits (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`,
246
+ `models-dev-cache: file-layer loaded ${metadata.size} model metadata entries (modelsJsonPath=${modelsJsonPath}, found=${fileFound})`,
199
247
  );
200
248
 
201
- return limits;
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: OpencodeClient): Promise<void> {
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, number>();
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<string, { limit?: { context?: number; input?: number } }>;
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
- const effective = resolveLimit(model?.limit);
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 limits${
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 = loadModelsDevLimitsFromFile();
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
- return fileCache.get(key);
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). */