@gmickel/gno 1.45.1 → 2.0.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 (236) hide show
  1. package/README.md +1 -1
  2. package/THIRD_PARTY_NOTICES.md +46 -0
  3. package/assets/skill/SKILL.md +7 -6
  4. package/assets/skill/cli-reference.md +14 -6
  5. package/assets/skill/mcp-reference.md +4 -1
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.0.0.zip.sha256 +1 -0
  9. package/browser-extension/dist/chunk-4tc9v0ja.js +74 -0
  10. package/browser-extension/dist/manifest.json +1 -1
  11. package/browser-extension/dist/preview.html +1 -1
  12. package/browser-extension/dist/service-worker.js +32 -33
  13. package/bunfig.toml +2 -0
  14. package/package.json +40 -26
  15. package/spec/cli.md +30 -11
  16. package/spec/db/schema.sql +146 -1
  17. package/spec/mcp.md +26 -0
  18. package/src/app/context-runtime-types.ts +3 -0
  19. package/src/app/context-runtime.ts +2 -0
  20. package/src/cli/commands/ask.ts +6 -1
  21. package/src/cli/commands/daemon.ts +21 -8
  22. package/src/cli/commands/embed.ts +77 -41
  23. package/src/cli/commands/mcp/install.ts +20 -0
  24. package/src/cli/commands/mcp/paths.ts +25 -0
  25. package/src/cli/commands/mcp/status.ts +6 -0
  26. package/src/cli/detach.ts +3 -2
  27. package/src/cli/program.ts +6 -0
  28. package/src/config/types.ts +3 -3
  29. package/src/converters/adapters/markitdownTs/adapter.ts +1 -2
  30. package/src/converters/adapters/officeparser/adapter.ts +1 -2
  31. package/src/converters/versions.ts +6 -8
  32. package/src/core/context-evidence.ts +8 -4
  33. package/src/core/job-manager.ts +95 -13
  34. package/src/core/network-boundary-inventory.ts +10 -0
  35. package/src/core/shutdown-budget.ts +45 -0
  36. package/src/embed/backlog.ts +107 -4
  37. package/src/embed/batch.ts +42 -2
  38. package/src/embed/fingerprint.ts +16 -0
  39. package/src/embed/retry.ts +113 -5
  40. package/src/embed/variant-backlog.ts +105 -0
  41. package/src/embed/variant-plan.ts +62 -0
  42. package/src/embed/variant-retry.ts +113 -0
  43. package/src/ingestion/graph-reconciliation.ts +327 -0
  44. package/src/ingestion/sync.ts +9 -272
  45. package/src/llm/http-inference.ts +6 -0
  46. package/src/llm/httpEmbedding.ts +37 -6
  47. package/src/llm/httpGeneration.ts +18 -3
  48. package/src/llm/httpRerank.ts +23 -5
  49. package/src/llm/inference-cancellation.ts +168 -0
  50. package/src/llm/inference-scope.ts +202 -0
  51. package/src/llm/lazy-ports.ts +115 -0
  52. package/src/llm/native-worker/client.ts +541 -0
  53. package/src/llm/native-worker/dispatcher.ts +228 -0
  54. package/src/llm/native-worker/embedding-identity.ts +33 -0
  55. package/src/llm/native-worker/entry.ts +173 -0
  56. package/src/llm/native-worker/errors.ts +32 -0
  57. package/src/llm/native-worker/evaluation.ts +16 -0
  58. package/src/llm/native-worker/owned-exit.ts +108 -0
  59. package/src/llm/native-worker/owner.ts +141 -0
  60. package/src/llm/native-worker/ports.ts +317 -0
  61. package/src/llm/native-worker/protocol.ts +442 -0
  62. package/src/llm/native-worker/runtime-config.ts +92 -0
  63. package/src/llm/nodeLlamaCpp/adapter.ts +77 -20
  64. package/src/llm/nodeLlamaCpp/embedding.ts +130 -46
  65. package/src/llm/nodeLlamaCpp/generation.ts +34 -5
  66. package/src/llm/nodeLlamaCpp/lifecycle-options.ts +99 -0
  67. package/src/llm/nodeLlamaCpp/lifecycle.ts +209 -204
  68. package/src/llm/nodeLlamaCpp/rerank-capacity.ts +111 -0
  69. package/src/llm/nodeLlamaCpp/rerank.ts +118 -27
  70. package/src/llm/nodeLlamaCpp/simulator-handle.ts +73 -0
  71. package/src/llm/nodeLlamaCpp/simulator-install.ts +124 -0
  72. package/src/llm/nodeLlamaCpp/simulator-session.ts +240 -0
  73. package/src/llm/nodeLlamaCpp/simulator-types.ts +80 -0
  74. package/src/llm/types.ts +35 -5
  75. package/src/mcp/context.ts +27 -0
  76. package/src/mcp/http-transport.ts +12 -10
  77. package/src/mcp/server.ts +3 -0
  78. package/src/mcp/tool-profile.ts +30 -8
  79. package/src/mcp/tools/context.ts +8 -11
  80. package/src/mcp/tools/embed.ts +1 -1
  81. package/src/mcp/tools/index-cmd.ts +1 -1
  82. package/src/mcp/tools/index.ts +10 -8
  83. package/src/mcp/tools/query.ts +14 -30
  84. package/src/mcp/tools/vsearch.ts +1 -1
  85. package/src/pipeline/answer.ts +23 -3
  86. package/src/pipeline/claim-verifier.ts +6 -0
  87. package/src/pipeline/expansion.ts +43 -40
  88. package/src/pipeline/explain.ts +6 -2
  89. package/src/pipeline/filters.ts +63 -0
  90. package/src/pipeline/fusion.ts +29 -9
  91. package/src/pipeline/graph-retrieval.ts +29 -9
  92. package/src/pipeline/hybrid.ts +198 -55
  93. package/src/pipeline/hydration.ts +161 -0
  94. package/src/pipeline/owner-fusion.ts +87 -0
  95. package/src/pipeline/rerank.ts +35 -11
  96. package/src/pipeline/search.ts +13 -2
  97. package/src/pipeline/types.ts +5 -3
  98. package/src/pipeline/vsearch.ts +87 -7
  99. package/src/sdk/client.ts +47 -3
  100. package/src/sdk/embed.ts +63 -39
  101. package/src/serve/background-runtime.ts +1 -1
  102. package/src/serve/context.ts +41 -56
  103. package/src/serve/embed-scheduler.ts +58 -35
  104. package/src/serve/public/components/IndexingProgress.tsx +46 -60
  105. package/src/serve/public/globals.built.css +1 -1
  106. package/src/serve/public/lib/shiki-language-ids.ts +14 -0
  107. package/src/serve/resident-admission.ts +36 -36
  108. package/src/serve/resident-background-work.ts +20 -2
  109. package/src/serve/resident-request.ts +11 -5
  110. package/src/serve/resident-runtime.ts +97 -61
  111. package/src/serve/resident-shutdown.ts +153 -0
  112. package/src/serve/routes/api.ts +3 -1
  113. package/src/serve/server.ts +47 -26
  114. package/src/store/migrations/028-vector-variants.ts +54 -0
  115. package/src/store/migrations/029-graph-reference-state.ts +77 -0
  116. package/src/store/migrations/index.ts +4 -0
  117. package/src/store/sqlite/adapter.ts +251 -183
  118. package/src/store/sqlite/eligibility.ts +174 -0
  119. package/src/store/sqlite/graph-edge-application.ts +66 -0
  120. package/src/store/sqlite/graph-reference-state.ts +194 -0
  121. package/src/store/sqlite/legacy-vector-ownership.ts +79 -0
  122. package/src/store/types.ts +80 -12
  123. package/src/store/vector/eligibility.ts +36 -0
  124. package/src/store/vector/freshness.ts +33 -6
  125. package/src/store/vector/lazy.ts +81 -0
  126. package/src/store/vector/sqlite-vec.ts +106 -54
  127. package/src/store/vector/stats.ts +14 -3
  128. package/src/store/vector/types.ts +35 -2
  129. package/src/store/vector/variant-search.ts +192 -0
  130. package/src/store/vector/variants.ts +451 -0
  131. package/vendor/converters/markitdown-ts/LICENSE +21 -0
  132. package/vendor/converters/markitdown-ts/dist/index.cjs +1180 -0
  133. package/vendor/converters/markitdown-ts/dist/index.d.cts +46 -0
  134. package/vendor/converters/markitdown-ts/dist/index.d.mts +46 -0
  135. package/vendor/converters/markitdown-ts/dist/index.d.ts +46 -0
  136. package/vendor/converters/markitdown-ts/dist/index.mjs +1152 -0
  137. package/vendor/converters/markitdown-ts/package.json +77 -0
  138. package/vendor/converters/officeparser/LICENSE +21 -0
  139. package/vendor/converters/officeparser/dist/OfficeConverter.d.ts +47 -0
  140. package/vendor/converters/officeparser/dist/OfficeConverter.js +76 -0
  141. package/vendor/converters/officeparser/dist/OfficeGenerator.d.ts +23 -0
  142. package/vendor/converters/officeparser/dist/OfficeGenerator.js +73 -0
  143. package/vendor/converters/officeparser/dist/OfficeParser.d.ts +106 -0
  144. package/vendor/converters/officeparser/dist/OfficeParser.js +332 -0
  145. package/vendor/converters/officeparser/dist/cli.d.ts +28 -0
  146. package/vendor/converters/officeparser/dist/cli.js +381 -0
  147. package/vendor/converters/officeparser/dist/defaults.d.ts +41 -0
  148. package/vendor/converters/officeparser/dist/defaults.js +218 -0
  149. package/vendor/converters/officeparser/dist/generators/BaseGenerator.d.ts +107 -0
  150. package/vendor/converters/officeparser/dist/generators/BaseGenerator.js +248 -0
  151. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.d.ts +82 -0
  152. package/vendor/converters/officeparser/dist/generators/ChunkingGenerator.js +797 -0
  153. package/vendor/converters/officeparser/dist/generators/CsvGenerator.d.ts +38 -0
  154. package/vendor/converters/officeparser/dist/generators/CsvGenerator.js +245 -0
  155. package/vendor/converters/officeparser/dist/generators/EpubGenerator.d.ts +43 -0
  156. package/vendor/converters/officeparser/dist/generators/EpubGenerator.js +315 -0
  157. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.d.ts +59 -0
  158. package/vendor/converters/officeparser/dist/generators/HtmlGenerator.js +1942 -0
  159. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.d.ts +96 -0
  160. package/vendor/converters/officeparser/dist/generators/MarkdownGenerator.js +1175 -0
  161. package/vendor/converters/officeparser/dist/generators/PdfGenerator.d.ts +22 -0
  162. package/vendor/converters/officeparser/dist/generators/PdfGenerator.js +194 -0
  163. package/vendor/converters/officeparser/dist/generators/RtfGenerator.d.ts +29 -0
  164. package/vendor/converters/officeparser/dist/generators/RtfGenerator.js +316 -0
  165. package/vendor/converters/officeparser/dist/generators/TextGenerator.d.ts +13 -0
  166. package/vendor/converters/officeparser/dist/generators/TextGenerator.js +201 -0
  167. package/vendor/converters/officeparser/dist/index.d.ts +60 -0
  168. package/vendor/converters/officeparser/dist/index.js +72 -0
  169. package/vendor/converters/officeparser/dist/index.mjs +18 -0
  170. package/vendor/converters/officeparser/dist/officeparser.browser.d.ts +2621 -0
  171. package/vendor/converters/officeparser/dist/officeparser.browser.iife.js +1336 -0
  172. package/vendor/converters/officeparser/dist/officeparser.browser.mjs +1335 -0
  173. package/vendor/converters/officeparser/dist/officeparser.browser.slim.d.ts +2621 -0
  174. package/vendor/converters/officeparser/dist/officeparser.browser.slim.iife.js +1336 -0
  175. package/vendor/converters/officeparser/dist/officeparser.browser.slim.mjs +1335 -0
  176. package/vendor/converters/officeparser/dist/parsers/CsvParser.d.ts +9 -0
  177. package/vendor/converters/officeparser/dist/parsers/CsvParser.js +115 -0
  178. package/vendor/converters/officeparser/dist/parsers/EpubParser.d.ts +8 -0
  179. package/vendor/converters/officeparser/dist/parsers/EpubParser.js +217 -0
  180. package/vendor/converters/officeparser/dist/parsers/ExcelParser.d.ts +32 -0
  181. package/vendor/converters/officeparser/dist/parsers/ExcelParser.js +736 -0
  182. package/vendor/converters/officeparser/dist/parsers/HtmlParser.d.ts +2 -0
  183. package/vendor/converters/officeparser/dist/parsers/HtmlParser.js +1287 -0
  184. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.d.ts +2 -0
  185. package/vendor/converters/officeparser/dist/parsers/MarkdownParser.js +1272 -0
  186. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.d.ts +31 -0
  187. package/vendor/converters/officeparser/dist/parsers/OpenOfficeParser.js +1819 -0
  188. package/vendor/converters/officeparser/dist/parsers/PdfParser.d.ts +67 -0
  189. package/vendor/converters/officeparser/dist/parsers/PdfParser.js +848 -0
  190. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.d.ts +32 -0
  191. package/vendor/converters/officeparser/dist/parsers/PowerPointParser.js +950 -0
  192. package/vendor/converters/officeparser/dist/parsers/RtfParser.d.ts +187 -0
  193. package/vendor/converters/officeparser/dist/parsers/RtfParser.js +1801 -0
  194. package/vendor/converters/officeparser/dist/parsers/WordParser.d.ts +79 -0
  195. package/vendor/converters/officeparser/dist/parsers/WordParser.js +1177 -0
  196. package/vendor/converters/officeparser/dist/sbom.cdx.json +1763 -0
  197. package/vendor/converters/officeparser/dist/types.d.ts +2507 -0
  198. package/vendor/converters/officeparser/dist/types.js +107 -0
  199. package/vendor/converters/officeparser/dist/utils/astUtils.d.ts +16 -0
  200. package/vendor/converters/officeparser/dist/utils/astUtils.js +33 -0
  201. package/vendor/converters/officeparser/dist/utils/chartUtils.d.ts +6 -0
  202. package/vendor/converters/officeparser/dist/utils/chartUtils.js +257 -0
  203. package/vendor/converters/officeparser/dist/utils/configUtils.d.ts +44 -0
  204. package/vendor/converters/officeparser/dist/utils/configUtils.js +315 -0
  205. package/vendor/converters/officeparser/dist/utils/dateUtils.d.ts +17 -0
  206. package/vendor/converters/officeparser/dist/utils/dateUtils.js +69 -0
  207. package/vendor/converters/officeparser/dist/utils/envUtils.d.ts +29 -0
  208. package/vendor/converters/officeparser/dist/utils/envUtils.js +152 -0
  209. package/vendor/converters/officeparser/dist/utils/errorUtils.d.ts +72 -0
  210. package/vendor/converters/officeparser/dist/utils/errorUtils.js +245 -0
  211. package/vendor/converters/officeparser/dist/utils/imageUtils.d.ts +66 -0
  212. package/vendor/converters/officeparser/dist/utils/imageUtils.js +133 -0
  213. package/vendor/converters/officeparser/dist/utils/mathUtils.d.ts +42 -0
  214. package/vendor/converters/officeparser/dist/utils/mathUtils.js +385 -0
  215. package/vendor/converters/officeparser/dist/utils/moduleLoader.d.ts +18 -0
  216. package/vendor/converters/officeparser/dist/utils/moduleLoader.js +106 -0
  217. package/vendor/converters/officeparser/dist/utils/ocrUtils.d.ts +42 -0
  218. package/vendor/converters/officeparser/dist/utils/ocrUtils.js +428 -0
  219. package/vendor/converters/officeparser/dist/utils/sanitize.d.ts +148 -0
  220. package/vendor/converters/officeparser/dist/utils/sanitize.js +344 -0
  221. package/vendor/converters/officeparser/dist/utils/sheetUtils.d.ts +7 -0
  222. package/vendor/converters/officeparser/dist/utils/sheetUtils.js +35 -0
  223. package/vendor/converters/officeparser/dist/utils/styleMapper.d.ts +36 -0
  224. package/vendor/converters/officeparser/dist/utils/styleMapper.js +224 -0
  225. package/vendor/converters/officeparser/dist/utils/xmlUtils.d.ts +163 -0
  226. package/vendor/converters/officeparser/dist/utils/xmlUtils.js +461 -0
  227. package/vendor/converters/officeparser/dist/utils/zipUtils.d.ts +134 -0
  228. package/vendor/converters/officeparser/dist/utils/zipUtils.js +337 -0
  229. package/vendor/converters/officeparser/package.json +147 -0
  230. package/vendor/converters/upstream-manifest.json +124 -0
  231. package/vendor/dependency-fixes/README.md +77 -0
  232. package/vendor/dependency-fixes/vendor-converters.py +83 -0
  233. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip +0 -0
  234. package/browser-extension/artifacts/gno-browser-clipper-v1.45.1.zip.sha256 +0 -1
  235. package/browser-extension/dist/chunk-627emwpj.js +0 -75
  236. /package/browser-extension/dist/{chunk-ydfx5d7p.css → chunk-z74y8n8c.css} +0 -0
@@ -1,16 +1,17 @@
1
+ import type { HttpInferenceOptions } from "./http-inference";
2
+ import type { InferenceOptions } from "./types";
1
3
  /**
2
4
  * HTTP-based embedding port implementation.
3
5
  * Calls OpenAI-compatible embedding endpoints.
4
6
  *
5
7
  * @module src/llm/httpEmbedding
6
8
  */
7
-
8
- import type { HttpInferenceOptions } from "./http-inference";
9
9
  import type { EmbeddingPort, LlmResult } from "./types";
10
10
 
11
11
  import { EgressDeniedError } from "../core/egress-enforcement";
12
12
  import { egressDeniedInferenceError, inferenceFailedError } from "./errors";
13
13
  import { requestHttpInference } from "./http-inference";
14
+ import { runHttpInference } from "./inference-scope";
14
15
 
15
16
  // ─────────────────────────────────────────────────────────────────────────────
16
17
  // Types
@@ -58,20 +59,35 @@ export class HttpEmbedding implements EmbeddingPort {
58
59
  }
59
60
  }
60
61
 
61
- async init(): Promise<LlmResult<void>> {
62
+ async init(options?: InferenceOptions): Promise<LlmResult<void>> {
62
63
  // Test connection with a simple embedding
63
- const result = await this.embed("test");
64
+ const result = await this.embed("test", options);
64
65
  if (!result.ok) {
65
66
  return result;
66
67
  }
67
68
  return { ok: true, value: undefined };
68
69
  }
69
70
 
70
- async embed(text: string): Promise<LlmResult<number[]>> {
71
+ async embed(
72
+ text: string,
73
+ options?: InferenceOptions
74
+ ): Promise<LlmResult<number[]>> {
75
+ return runHttpInference(
76
+ options,
77
+ (operational) => this.embedRequest(text, operational),
78
+ this.requestOptions.inferenceTimeout
79
+ );
80
+ }
81
+
82
+ private async embedRequest(
83
+ text: string,
84
+ options: InferenceOptions
85
+ ): Promise<LlmResult<number[]>> {
71
86
  try {
72
87
  const response = await requestHttpInference(
73
88
  this.apiUrl,
74
89
  {
90
+ signal: options.signal,
75
91
  method: "POST",
76
92
  headers: {
77
93
  "Content-Type": "application/json",
@@ -128,11 +144,26 @@ export class HttpEmbedding implements EmbeddingPort {
128
144
  }
129
145
  }
130
146
 
131
- async embedBatch(texts: string[]): Promise<LlmResult<number[][]>> {
147
+ async embedBatch(
148
+ texts: string[],
149
+ options?: InferenceOptions
150
+ ): Promise<LlmResult<number[][]>> {
151
+ return runHttpInference(
152
+ options,
153
+ (operational) => this.embedBatchRequest(texts, operational),
154
+ this.requestOptions.inferenceTimeout
155
+ );
156
+ }
157
+
158
+ private async embedBatchRequest(
159
+ texts: string[],
160
+ options: InferenceOptions
161
+ ): Promise<LlmResult<number[][]>> {
132
162
  try {
133
163
  const response = await requestHttpInference(
134
164
  this.apiUrl,
135
165
  {
166
+ signal: options.signal,
136
167
  method: "POST",
137
168
  headers: {
138
169
  "Content-Type": "application/json",
@@ -1,11 +1,11 @@
1
+ import type { HttpInferenceOptions } from "./http-inference";
2
+ import type { InferenceOptions } from "./types";
1
3
  /**
2
4
  * HTTP-based generation port implementation.
3
5
  * Calls OpenAI-compatible chat completion endpoints.
4
6
  *
5
7
  * @module src/llm/httpGeneration
6
8
  */
7
-
8
- import type { HttpInferenceOptions } from "./http-inference";
9
9
  import type { GenerationPort, GenParams, LlmResult } from "./types";
10
10
 
11
11
  import { EgressDeniedError } from "../core/egress-enforcement";
@@ -15,6 +15,7 @@ import {
15
15
  structuredOutputUnavailableError,
16
16
  } from "./errors";
17
17
  import { requestHttpInference } from "./http-inference";
18
+ import { runHttpInference } from "./inference-scope";
18
19
 
19
20
  // ─────────────────────────────────────────────────────────────────────────────
20
21
  // Types
@@ -71,7 +72,20 @@ export class HttpGeneration implements GenerationPort {
71
72
 
72
73
  async generate(
73
74
  prompt: string,
74
- params?: GenParams
75
+ params?: GenParams,
76
+ options?: InferenceOptions
77
+ ): Promise<LlmResult<string>> {
78
+ return runHttpInference(
79
+ options,
80
+ (operational) => this.generateRequest(prompt, params, operational),
81
+ this.requestOptions.inferenceTimeout
82
+ );
83
+ }
84
+
85
+ private async generateRequest(
86
+ prompt: string,
87
+ params: GenParams | undefined,
88
+ options: InferenceOptions
75
89
  ): Promise<LlmResult<string>> {
76
90
  if (params?.jsonSchema) {
77
91
  return {
@@ -83,6 +97,7 @@ export class HttpGeneration implements GenerationPort {
83
97
  const response = await requestHttpInference(
84
98
  this.apiUrl,
85
99
  {
100
+ signal: options.signal,
86
101
  method: "POST",
87
102
  headers: {
88
103
  "Content-Type": "application/json",
@@ -1,16 +1,17 @@
1
+ import type { HttpInferenceOptions } from "./http-inference";
2
+ import type { InferenceOptions } from "./types";
1
3
  /**
2
4
  * HTTP-based rerank port implementation.
3
5
  * Calls OpenAI-compatible completions endpoints for reranking.
4
6
  *
5
7
  * @module src/llm/httpRerank
6
8
  */
7
-
8
- import type { HttpInferenceOptions } from "./http-inference";
9
9
  import type { LlmResult, RerankPort, RerankScore } from "./types";
10
10
 
11
11
  import { EgressDeniedError } from "../core/egress-enforcement";
12
12
  import { egressDeniedInferenceError, inferenceFailedError } from "./errors";
13
13
  import { requestHttpInference } from "./http-inference";
14
+ import { runHttpInference } from "./inference-scope";
14
15
 
15
16
  // ─────────────────────────────────────────────────────────────────────────────
16
17
  // Types
@@ -63,7 +64,20 @@ export class HttpRerank implements RerankPort {
63
64
 
64
65
  async rerank(
65
66
  query: string,
66
- documents: string[]
67
+ documents: string[],
68
+ options?: InferenceOptions
69
+ ): Promise<LlmResult<RerankScore[]>> {
70
+ return runHttpInference(
71
+ options,
72
+ (operational) => this.rerankRequest(query, documents, operational),
73
+ this.requestOptions.inferenceTimeout
74
+ );
75
+ }
76
+
77
+ private async rerankRequest(
78
+ query: string,
79
+ documents: string[],
80
+ options: InferenceOptions
67
81
  ): Promise<LlmResult<RerankScore[]>> {
68
82
  if (documents.length === 0) {
69
83
  return { ok: true, value: [] };
@@ -74,7 +88,7 @@ export class HttpRerank implements RerankPort {
74
88
  const prompts = documents.map((doc) => this.buildPrompt(query, doc));
75
89
 
76
90
  // Score all documents in a single batch request
77
- const scoresResult = await this.scoreBatch(prompts);
91
+ const scoresResult = await this.scoreBatch(prompts, options);
78
92
 
79
93
  if (!scoresResult.ok) {
80
94
  return { ok: false, error: scoresResult.error };
@@ -112,11 +126,15 @@ export class HttpRerank implements RerankPort {
112
126
  return `<Instruct>: ${this.instruction}\n<Query>: ${query}\n<Document>: ${document}\n<Score>:`;
113
127
  }
114
128
 
115
- private async scoreBatch(prompts: string[]): Promise<LlmResult<number[]>> {
129
+ private async scoreBatch(
130
+ prompts: string[],
131
+ options: InferenceOptions
132
+ ): Promise<LlmResult<number[]>> {
116
133
  try {
117
134
  const response = await requestHttpInference(
118
135
  this.apiUrl,
119
136
  {
137
+ signal: options.signal,
120
138
  method: "POST",
121
139
  headers: {
122
140
  "Content-Type": "application/json",
@@ -0,0 +1,168 @@
1
+ /** Internal lifetime contract shared by inference owners. Cancellation settles
2
+ * the caller once; only native settlement releases active ownership. Wiring the
3
+ * signal into a backend is the owner's responsibility, not proof of interruption.
4
+ */
5
+ import type { LlmError } from "./errors";
6
+ import type { InferenceOptions, LlmResult } from "./types";
7
+
8
+ import { llmError } from "./errors";
9
+
10
+ const MAX_TIMER_MS = 2_147_483_647;
11
+ export type InferencePhase =
12
+ | "queued"
13
+ | "native-active"
14
+ | "response-pending"
15
+ | "settled";
16
+
17
+ /** Stable structural discriminator; do not inspect human-readable messages. */
18
+ export function isInferenceCancellation(error: LlmError): boolean {
19
+ return (
20
+ error.code === "INFERENCE_FAILED" &&
21
+ typeof error.cause === "object" &&
22
+ error.cause !== null &&
23
+ "name" in error.cause &&
24
+ error.cause.name === "AbortError"
25
+ );
26
+ }
27
+
28
+ export class InferenceSettlement<T> {
29
+ private state: InferencePhase = "queued";
30
+ private delivered = false;
31
+ private executionStarted = false;
32
+ private executionDeadline?: number;
33
+ private result?: LlmResult<T>;
34
+ private deadlineTimer?: ReturnType<typeof setTimeout>;
35
+ private executionTimer?: ReturnType<typeof setTimeout>;
36
+ private readonly controller = new AbortController();
37
+ private readonly delivery = Promise.withResolvers<LlmResult<T>>();
38
+ readonly completion = this.delivery.promise;
39
+ readonly signal = this.controller.signal;
40
+
41
+ constructor(
42
+ private readonly options: InferenceOptions = {},
43
+ private readonly inferenceTimeout = 30_000
44
+ ) {
45
+ if (
46
+ !Number.isInteger(inferenceTimeout) ||
47
+ inferenceTimeout < 1 ||
48
+ inferenceTimeout > MAX_TIMER_MS
49
+ )
50
+ throw new RangeError("Invalid inference timeout");
51
+ if (
52
+ options.deadlineAt !== undefined &&
53
+ (!Number.isSafeInteger(options.deadlineAt) || options.deadlineAt < 0)
54
+ )
55
+ throw new RangeError("Invalid inference deadline");
56
+ options.signal?.addEventListener("abort", this.onAbort, { once: true });
57
+ if (options.signal?.aborted) this.cancel("abort");
58
+ this.checkDeadline();
59
+ }
60
+
61
+ get phase(): InferencePhase {
62
+ return this.state;
63
+ }
64
+ get ownsNative(): boolean {
65
+ return this.state === "native-active";
66
+ }
67
+
68
+ /** Claim before starting model/context load, including noncooperative creation. */
69
+ startNative(): boolean {
70
+ this.checkDeadline();
71
+ if (this.state !== "queued" || this.delivered) return false;
72
+ this.state = "native-active";
73
+ return true;
74
+ }
75
+
76
+ /** Called immediately before evaluation, after loading. Never resets a timer. */
77
+ startExecution(): boolean {
78
+ this.checkDeadline();
79
+ if (!this.ownsNative || this.delivered || this.executionStarted)
80
+ return false;
81
+ this.executionStarted = true;
82
+ this.executionDeadline = performance.now() + this.inferenceTimeout;
83
+ this.executionTimer = setTimeout(
84
+ () => this.cancel("timeout"),
85
+ this.inferenceTimeout
86
+ );
87
+ return true;
88
+ }
89
+
90
+ /** Call only after the native promise settles or owned-child exit is confirmed.
91
+ * A cancellation acknowledgement alone is insufficient to release the lease.
92
+ */
93
+ nativeSettled(result: LlmResult<T>): void {
94
+ if (!this.ownsNative) return;
95
+ this.checkDeadline();
96
+ clearTimeout(this.executionTimer);
97
+ this.executionDeadline = undefined;
98
+ this.result = result;
99
+ this.state = this.delivered ? "settled" : "response-pending";
100
+ }
101
+
102
+ /** Keep caller deadline/abort live through response validation and publication. */
103
+ publish(): boolean {
104
+ this.checkDeadline();
105
+ if (this.state !== "response-pending" || this.delivered || !this.result)
106
+ return false;
107
+ this.deliver(this.result);
108
+ return true;
109
+ }
110
+
111
+ /** Fail caller delivery while retaining native ownership until exit/settlement. */
112
+ fail(error: LlmError): void {
113
+ if (!this.delivered) this.deliver({ ok: false, error });
114
+ }
115
+
116
+ cancel(reason: "abort" | "timeout"): void {
117
+ if (this.delivered) return;
118
+ const cause = new DOMException(
119
+ reason === "abort"
120
+ ? "Inference cancelled"
121
+ : "Inference deadline exceeded",
122
+ reason === "abort" ? "AbortError" : "TimeoutError"
123
+ );
124
+ // Settle first: synchronous abort listeners cannot publish a late success.
125
+ this.deliver({
126
+ ok: false,
127
+ error: llmError(reason === "abort" ? "INFERENCE_FAILED" : "TIMEOUT", {
128
+ message: cause.message,
129
+ retryable: false,
130
+ cause,
131
+ }),
132
+ });
133
+ this.controller.abort(cause);
134
+ }
135
+
136
+ private readonly onAbort = (): void => {
137
+ this.cancel("abort");
138
+ };
139
+
140
+ private checkDeadline(): void {
141
+ clearTimeout(this.deadlineTimer);
142
+ if (this.delivered) return;
143
+ if (
144
+ this.executionDeadline !== undefined &&
145
+ performance.now() >= this.executionDeadline
146
+ ) {
147
+ this.cancel("timeout");
148
+ return;
149
+ }
150
+ if (this.options.deadlineAt === undefined) return;
151
+ const remaining = this.options.deadlineAt - Date.now();
152
+ if (remaining <= 0) this.cancel("timeout");
153
+ else
154
+ this.deadlineTimer = setTimeout(
155
+ () => this.checkDeadline(),
156
+ Math.min(remaining, MAX_TIMER_MS)
157
+ );
158
+ }
159
+
160
+ private deliver(result: LlmResult<T>): void {
161
+ this.delivered = true;
162
+ if (!this.ownsNative) this.state = "settled";
163
+ clearTimeout(this.deadlineTimer);
164
+ clearTimeout(this.executionTimer);
165
+ this.options.signal?.removeEventListener("abort", this.onAbort);
166
+ this.delivery.resolve(result);
167
+ }
168
+ }
@@ -0,0 +1,202 @@
1
+ // Bun has no native async-context API; AsyncLocalStorage isolates concurrent callers.
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+
4
+ import type { InferenceOptions, LlmResult } from "./types";
5
+
6
+ import { InferenceSettlement } from "./inference-cancellation";
7
+
8
+ interface RequestInferenceScope extends InferenceOptions {
9
+ parent?: RequestInferenceScope;
10
+ failure?: DOMException;
11
+ controller?: AbortController;
12
+ }
13
+ const scope = new AsyncLocalStorage<RequestInferenceScope>();
14
+ // Internal scheduling class, separate from public inference options/model inputs.
15
+ const background = new AsyncLocalStorage<boolean>();
16
+
17
+ export function isBackgroundInference(): boolean {
18
+ return background.getStore() === true;
19
+ }
20
+
21
+ export function withBackgroundInference<T>(
22
+ operation: () => Promise<T>
23
+ ): Promise<T> {
24
+ return background.run(true, operation);
25
+ }
26
+
27
+ export function recordInferenceTimeout(): void {
28
+ let current = scope.getStore();
29
+ while (current) {
30
+ current.failure = new DOMException(
31
+ "Inference deadline exceeded",
32
+ "TimeoutError"
33
+ );
34
+ current.controller?.abort(current.failure);
35
+ current = current.parent;
36
+ }
37
+ }
38
+
39
+ /** Operational metadata only: never serialized with model inputs. */
40
+ export function inferenceOptions(
41
+ options: InferenceOptions = {}
42
+ ): InferenceOptions {
43
+ if (
44
+ options.deadlineAt !== undefined &&
45
+ (!Number.isSafeInteger(options.deadlineAt) || options.deadlineAt < 0)
46
+ )
47
+ throw new RangeError("Invalid inference deadline");
48
+ const parent = scope.getStore();
49
+ const signals = [
50
+ parent?.failure ? AbortSignal.abort(parent.failure) : parent?.signal,
51
+ options.signal,
52
+ ].filter((signal): signal is AbortSignal => signal !== undefined);
53
+ const deadlines = [parent?.deadlineAt, options.deadlineAt].filter(
54
+ (deadline): deadline is number => deadline !== undefined
55
+ );
56
+ return {
57
+ signal: signals.length > 1 ? AbortSignal.any(signals) : signals[0],
58
+ deadlineAt: deadlines.length ? Math.min(...deadlines) : undefined,
59
+ };
60
+ }
61
+
62
+ export function assertInferenceActive(options: InferenceOptions = {}): void {
63
+ if (scope.getStore()?.failure) throw scope.getStore()!.failure;
64
+ const current = inferenceOptions(options);
65
+ if (current.signal?.aborted)
66
+ throw new DOMException("Inference cancelled", "AbortError");
67
+ if (current.deadlineAt !== undefined && Date.now() >= current.deadlineAt)
68
+ throw new DOMException("Inference deadline exceeded", "TimeoutError");
69
+ }
70
+
71
+ export async function withInferenceScope<T>(
72
+ options: InferenceOptions,
73
+ operation: () => Promise<T>
74
+ ): Promise<T> {
75
+ const inherited = inferenceOptions(options);
76
+ const controller = new AbortController();
77
+ return scope.run(
78
+ {
79
+ ...inherited,
80
+ signal: inherited.signal
81
+ ? AbortSignal.any([inherited.signal, controller.signal])
82
+ : controller.signal,
83
+ controller,
84
+ parent: scope.getStore(),
85
+ },
86
+ async () => {
87
+ assertInferenceActive();
88
+ const result = await operation();
89
+ assertInferenceActive();
90
+ return result;
91
+ }
92
+ );
93
+ }
94
+
95
+ /** HTTP operations own their fetch/body until settlement; abort reaches fetch. */
96
+ export function runHttpInference<T>(
97
+ options: InferenceOptions | undefined,
98
+ operation: (options: InferenceOptions) => Promise<LlmResult<T>>,
99
+ inferenceTimeout = 30_000
100
+ ): Promise<LlmResult<T>> {
101
+ const settlement = new InferenceSettlement<T>(
102
+ inferenceOptions(options),
103
+ inferenceTimeout
104
+ );
105
+ if (settlement.startNative()) {
106
+ settlement.startExecution();
107
+ void operation({ signal: settlement.signal }).then(
108
+ (result) => {
109
+ settlement.nativeSettled(result);
110
+ settlement.publish();
111
+ },
112
+ (cause: unknown) => {
113
+ settlement.nativeSettled({
114
+ ok: false,
115
+ error: {
116
+ code: "INFERENCE_FAILED",
117
+ message: "HTTP inference failed",
118
+ retryable: false,
119
+ cause,
120
+ },
121
+ });
122
+ settlement.publish();
123
+ }
124
+ );
125
+ }
126
+ return settlement.completion.then((result) => {
127
+ if (!result.ok && result.error.code === "TIMEOUT") recordInferenceTimeout();
128
+ return result;
129
+ });
130
+ }
131
+
132
+ /** Never convert cancellation or timeout into retrieval fallback. */
133
+ export function assertInferenceResult<T>(result: LlmResult<T>): void {
134
+ assertInferenceActive();
135
+ if (
136
+ !result.ok &&
137
+ (result.error.code === "TIMEOUT" ||
138
+ (typeof result.error.cause === "object" &&
139
+ result.error.cause !== null &&
140
+ "name" in result.error.cause &&
141
+ result.error.cause.name === "AbortError"))
142
+ )
143
+ throw new DOMException(
144
+ result.error.message,
145
+ result.error.code === "TIMEOUT" ? "TimeoutError" : "AbortError"
146
+ );
147
+ }
148
+
149
+ /** Owned cleanup must not inherit an expired caller, or block its delivery. */
150
+ export function finishInferenceCleanup(
151
+ operation: () => Promise<void>
152
+ ): Promise<void> {
153
+ const cancelled =
154
+ scope.getStore()?.failure ||
155
+ scope.getStore()?.signal?.aborted ||
156
+ (scope.getStore()?.deadlineAt !== undefined &&
157
+ Date.now() >= scope.getStore()!.deadlineAt!);
158
+ if (!cancelled) return operation();
159
+ void scope.run({}, operation).catch(() => {});
160
+ return Promise.resolve();
161
+ }
162
+
163
+ /** Accepted jobs own their lifetime independently of their initiating request. */
164
+ export function withOwnedInferenceScope<T>(
165
+ options: InferenceOptions,
166
+ operation: () => Promise<T>
167
+ ): Promise<T> {
168
+ return scope.run({}, () => withInferenceScope(options, operation));
169
+ }
170
+
171
+ /** A cancelled queued reader releases only its eventual grant, never another caller's. */
172
+ export async function acquireInferencePermit(
173
+ acquire: () => Promise<() => void>
174
+ ): Promise<() => void> {
175
+ const lifetime = new InferenceSettlement<() => void>(inferenceOptions());
176
+ if (lifetime.startNative())
177
+ void acquire().then(
178
+ (release) => {
179
+ lifetime.nativeSettled({ ok: true, value: release });
180
+ if (!lifetime.publish()) release();
181
+ },
182
+ (cause: unknown) => {
183
+ lifetime.nativeSettled({
184
+ ok: false,
185
+ error: {
186
+ code: "INFERENCE_FAILED",
187
+ message: "Request admission failed",
188
+ retryable: false,
189
+ cause,
190
+ },
191
+ });
192
+ lifetime.publish();
193
+ }
194
+ );
195
+ const result = await lifetime.completion;
196
+ if (!result.ok)
197
+ throw new DOMException(
198
+ result.error.message,
199
+ result.error.code === "TIMEOUT" ? "TimeoutError" : "AbortError"
200
+ );
201
+ return result.value;
202
+ }
@@ -0,0 +1,115 @@
1
+ /** Defer model resolution/download until an actual inference operation. */
2
+ import type {
3
+ EmbeddingPort,
4
+ GenerationPort,
5
+ LlmResult,
6
+ RerankPort,
7
+ } from "./types";
8
+
9
+ import { inferenceFailedError } from "./errors";
10
+ import { isHttpGenUri } from "./httpGeneration";
11
+
12
+ function lazyOwner<T extends { dispose(): Promise<void> }>(
13
+ modelUri: string,
14
+ create: () => Promise<LlmResult<T>>
15
+ ) {
16
+ let current: T | undefined;
17
+ let pending: Promise<LlmResult<T>> | undefined;
18
+ let disposed = false;
19
+ const closed = (): LlmResult<T> => ({
20
+ ok: false,
21
+ error: inferenceFailedError(modelUri, new Error("Port is disposed")),
22
+ });
23
+ return {
24
+ peek: () => current,
25
+ get(): Promise<LlmResult<T>> {
26
+ if (disposed) return Promise.resolve(closed());
27
+ if (current) return Promise.resolve({ ok: true, value: current });
28
+ pending ??= create()
29
+ .then(async (result) => {
30
+ if (disposed) {
31
+ if (result.ok) await result.value.dispose();
32
+ return closed();
33
+ }
34
+ if (result.ok) current = result.value;
35
+ return result;
36
+ })
37
+ .finally(() => {
38
+ pending = undefined;
39
+ });
40
+ return pending;
41
+ },
42
+ async dispose(): Promise<void> {
43
+ if (disposed) return;
44
+ disposed = true;
45
+ await pending;
46
+ const owned = current;
47
+ current = undefined;
48
+ await owned?.dispose();
49
+ },
50
+ };
51
+ }
52
+
53
+ export function lazyEmbeddingPort(
54
+ modelUri: string,
55
+ create: () => Promise<LlmResult<EmbeddingPort>>
56
+ ): EmbeddingPort {
57
+ const owner = lazyOwner(modelUri, create);
58
+ return {
59
+ modelUri,
60
+ async init() {
61
+ const port = await owner.get();
62
+ return port.ok ? port.value.init() : port;
63
+ },
64
+ async embed(text) {
65
+ const port = await owner.get();
66
+ return port.ok ? port.value.embed(text) : port;
67
+ },
68
+ async embedBatch(texts) {
69
+ const port = await owner.get();
70
+ return port.ok ? port.value.embedBatch(texts) : port;
71
+ },
72
+ dimensions() {
73
+ const port = owner.peek();
74
+ if (!port)
75
+ throw new Error(
76
+ "Call init() or embed() first to initialize dimensions"
77
+ );
78
+ return port.dimensions();
79
+ },
80
+ getIdentity: () => owner.peek()?.getIdentity?.(),
81
+ dispose: () => owner.dispose(),
82
+ };
83
+ }
84
+
85
+ export function lazyGenerationPort(
86
+ modelUri: string,
87
+ create: () => Promise<LlmResult<GenerationPort>>
88
+ ): GenerationPort {
89
+ const owner = lazyOwner(modelUri, create);
90
+ return {
91
+ modelUri,
92
+ // These are the fixed native/HTTP adapter contracts; no probing required.
93
+ structuredOutput: isHttpGenUri(modelUri) ? "none" : "json_schema",
94
+ async generate(prompt, params) {
95
+ const port = await owner.get();
96
+ return port.ok ? port.value.generate(prompt, params) : port;
97
+ },
98
+ dispose: () => owner.dispose(),
99
+ };
100
+ }
101
+
102
+ export function lazyRerankPort(
103
+ modelUri: string,
104
+ create: () => Promise<LlmResult<RerankPort>>
105
+ ): RerankPort {
106
+ const owner = lazyOwner(modelUri, create);
107
+ return {
108
+ modelUri,
109
+ async rerank(query, documents) {
110
+ const port = await owner.get();
111
+ return port.ok ? port.value.rerank(query, documents) : port;
112
+ },
113
+ dispose: () => owner.dispose(),
114
+ };
115
+ }