@graphorin/provider 0.6.1 → 0.7.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 (74) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/dist/adapters/llamacpp-server.d.ts +1 -1
  4. package/dist/adapters/llamacpp-server.js.map +1 -1
  5. package/dist/adapters/ollama.d.ts.map +1 -1
  6. package/dist/adapters/ollama.js +19 -6
  7. package/dist/adapters/ollama.js.map +1 -1
  8. package/dist/adapters/vercel.d.ts +1 -1
  9. package/dist/adapters/vercel.d.ts.map +1 -1
  10. package/dist/adapters/vercel.js +111 -33
  11. package/dist/adapters/vercel.js.map +1 -1
  12. package/dist/errors/errors.d.ts +1 -1
  13. package/dist/errors/errors.js +1 -1
  14. package/dist/errors/errors.js.map +1 -1
  15. package/dist/index.js +0 -6
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal/http.js +111 -7
  18. package/dist/internal/http.js.map +1 -1
  19. package/dist/internal/openai-shaped.js +21 -4
  20. package/dist/internal/openai-shaped.js.map +1 -1
  21. package/dist/middleware/with-fallback.js +1 -1
  22. package/dist/middleware/with-rate-limit.d.ts +21 -8
  23. package/dist/middleware/with-rate-limit.d.ts.map +1 -1
  24. package/dist/middleware/with-rate-limit.js +65 -12
  25. package/dist/middleware/with-rate-limit.js.map +1 -1
  26. package/dist/middleware/with-retry.js +1 -1
  27. package/dist/package.js +1 -1
  28. package/dist/package.js.map +1 -1
  29. package/package.json +18 -16
  30. package/src/adapters/index.ts +14 -0
  31. package/src/adapters/llamacpp-server.ts +102 -0
  32. package/src/adapters/ollama.ts +382 -0
  33. package/src/adapters/openai-compatible.ts +95 -0
  34. package/src/adapters/vercel-messages.ts +308 -0
  35. package/src/adapters/vercel.ts +706 -0
  36. package/src/counters/anthropic-wire.ts +199 -0
  37. package/src/counters/anthropic.ts +114 -0
  38. package/src/counters/bedrock.ts +46 -0
  39. package/src/counters/dispatcher.ts +127 -0
  40. package/src/counters/global.ts +39 -0
  41. package/src/counters/google.ts +46 -0
  42. package/src/counters/heuristic.ts +107 -0
  43. package/src/counters/index.ts +35 -0
  44. package/src/counters/js-tiktoken.ts +135 -0
  45. package/src/counters/serialize.ts +85 -0
  46. package/src/errors/errors.ts +316 -0
  47. package/src/errors/index.ts +20 -0
  48. package/src/index.ts +42 -0
  49. package/src/internal/abort.ts +30 -0
  50. package/src/internal/http.ts +388 -0
  51. package/src/internal/openai-shaped.ts +555 -0
  52. package/src/internal/sse.ts +112 -0
  53. package/src/internal/url-utils.ts +20 -0
  54. package/src/middleware/compose.ts +213 -0
  55. package/src/middleware/index.ts +37 -0
  56. package/src/middleware/production-hook.ts +47 -0
  57. package/src/middleware/with-cost-limit.ts +131 -0
  58. package/src/middleware/with-cost-tracking.ts +216 -0
  59. package/src/middleware/with-fallback.ts +157 -0
  60. package/src/middleware/with-rate-limit.ts +306 -0
  61. package/src/middleware/with-redaction.ts +671 -0
  62. package/src/middleware/with-retry.ts +274 -0
  63. package/src/middleware/with-tracing.ts +117 -0
  64. package/src/model-tier/classify.ts +125 -0
  65. package/src/model-tier/index.ts +11 -0
  66. package/src/provider.ts +121 -0
  67. package/src/reasoning/apply-policy.ts +89 -0
  68. package/src/reasoning/classify-contract.ts +120 -0
  69. package/src/reasoning/index.ts +21 -0
  70. package/src/reasoning/retention.ts +64 -0
  71. package/src/tool-examples.ts +54 -0
  72. package/src/trust/classify-local-provider.ts +254 -0
  73. package/src/trust/index.ts +14 -0
  74. package/dist/adapters/index.js +0 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @graphorin/provider
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-072: every export map's `import` condition becomes `default`, and the Node floor rises to `>=22.12.0`.
8
+
9
+ CJS consumers previously hit a bewildering `ERR_PACKAGE_PATH_NOT_EXPORTED` instead of a clear ESM-only signal. With the `default` condition, plain `require('@graphorin/core')` works via Node's stable `require(esm)` - which shipped in 22.12, hence the engines bump across every workspace manifest (packages, examples, benchmarks, docs; enforced by the widened mvp-readiness sweep). No dual-instance hazard: there is no CJS build, `require()` returns the same ESM module instance. ESM consumers are unaffected (`default` serves both paths; `types` stays first). The pack gate now runs attw under the full `node16` profile (was `esm-only`) and adds a runtime `require(esm)` smoke against the packed tarballs. Installs on Node 22.0-22.11 with `engine-strict` will refuse - upgrade Node (see the migration guide).
10
+
11
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-024: thinking-block signatures now actually round-trip - the whole retention pipeline was dead because nothing captured them.
12
+
13
+ `ProviderEvent` gains a `{type: 'reasoning-end', meta?: ReasoningContentMeta}` terminator (per-block, matching both AI SDK generations). The vercel adapter maps v4 `reasoning-signature`/`redacted-reasoning` chunks and v7 `reasoning-end` (`providerMetadata.anthropic.signature`/`.redactedData`) onto it; `reasoning-start` stays a no-op. The agent runtime flushes buffered deltas into per-block `ReasoningContent` parts carrying the meta (redacted blocks become meta-only parts), and the step assembles those parts instead of one meta-less collapse - adapters without block structure keep the collapsed fallback. Downstream, the already-shipped chain finally engages: `applyReasoningPolicy('pass-through-claude')` retains the signed parts and `toAssistantPart` emits `providerOptions.anthropic.signature`, so multi-step tool use with Anthropic extended thinking replays each block byte-equal (pinned end-to-end: the step-2 request carries both signatures of a two-block step-1). Known scope limit: the one-shot `generate()` path still returns no reasoning (`ProviderResponse` has no field for it). MIGRATION: external exhaustive switches over `ProviderEvent` need a case for `'reasoning-end'`; transcripts may now carry several reasoning parts per step instead of one.
14
+
15
+ - [#154](https://github.com/o-stepper/graphorin/pull/154) [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04) Thanks [@o-stepper](https://github.com/o-stepper)! - W-023: the vercel adapter's streaming errors join the canonical taxonomy; transient failures become retryable/fallback-eligible.
16
+
17
+ The AI SDK never throws from `streamText()` synchronously - 429/500/529 arrive as in-band `{type:'error'}` chunks, which the adapter mapped to an inert `kind: 'unknown'` after an eagerly-emitted `stream-start`, so `withRetry` could never restart (PS-1), `withFallback` never switched, and the agent fallback chain treated the failure as ineligible: a transient 529 on a streaming step failed the whole run. Now: `stream-start` is deferred until the first REAL mapped event; an error chunk BEFORE any content THROWS a typed `ProviderHttpError` (status/errorKind/headers lifted from the chunk - pre-content 429/529 retry and fall back, with `retry-after` honoured); abort-shaped error chunks finish as `'aborted'` (never retried); a MID-stream error chunk yields a classified `ProviderErrorKind` (so the agent's per-step fallback acts on rate-limit/capacity) and the stream finishes with `finishReason: 'error'` instead of a synthetic `'stop'` with zero usage (PS-4 parity). BREAKING for consumers that relied on a yield-first error event for a first-chunk failure or on `stream-start` always preceding the throw.
18
+
19
+ - [#160](https://github.com/o-stepper/graphorin/pull/160) [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156) Thanks [@o-stepper](https://github.com/o-stepper)! - W-095: the local adapters can finally put images on the wire - and never drop multimodal content silently again. With `capabilities: { multimodal: true }`, `openAICompatibleAdapter` / `llamaCppServerAdapter` emit OpenAI `content` parts arrays (`image_url` with bytes as a data URI - default mime `image/png` - and `URL`s passed through as strings; the SERVER dereferences, the adapter never fetches), and `ollamaAdapter` fills the native per-message `images` array with raw base64 (URL images cannot be inlined on that path and are dropped loudly). Audio/file parts have no portable wire form on OpenAI-compatible servers and keep being dropped - now with a WARN. With default capabilities the wire stays byte-identical (flat string content), and dropping ANY non-text part triggers exactly one WARN per adapter instance naming the dropped kinds and pointing at `capabilities.multimodal`.
20
+
21
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - `withRateLimit` gains an optional `tokensPerMinute` budget alongside RPM (W-145): for agentic workloads the binding provider limit is TPM, and a 150k-token compacted transcript used to occupy the same single slot as a 200-token reranker call. Each request now reserves its estimated token weight (default heuristic `ceil(textChars/4) + maxTokens`, or a pluggable `estimateTokens` - wire `createDefaultCounter` for provider-accurate weights) from a second bucket whose capacity is the full minute budget. Throw mode reports the max of the RPM/TPM waits in `retryAfterMs`; queue mode grants FIFO only when both dimensions fit, with over-budget weights clamped to the bucket capacity so a huge request degrades to "wait for a full bucket" instead of deadlocking the queue. Without the option, behaviour is byte-identical to the RPM-only limiter.
22
+
23
+ ### Patch Changes
24
+
25
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - TSDoc `{@link}` hygiene sweep (W-130): all 55 broken links found by TypeDoc's now-enabled `validation.invalidLink` are fixed - two resolved to their real targets (`GraphorinMCPError` was misnamed `MCPError`), the rest (cross-package, `import()`-form, unexported-constant, and DOM-type references that have never rendered as hrefs) converted to plain inline code. The docs build now fails on any new broken `{@link}` via a scoped gate.
26
+
27
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Tarballs now ship `src/` so the published `dist/**/*.d.ts.map` files actually work (W-136): the maps referenced `../src/*.ts` that the `files` whitelist excluded, so go-to-definition fell back into `.d.ts` and the shipped maps were dead weight. The pack gate gains a `map-integrity` leg: every source referenced by a shipped map must resolve inside the tarball (or be embedded via `sourcesContent`), with an anti-vacuous guard - a package whose tsdown config emits declaration maps must contain a non-zero number of `.d.ts.map` files, so a cache-restored dist that silently dropped maps fails the gate instead of passing vacuously. `mvp-readiness` now requires `src` in every publishable `files` array.
28
+
29
+ - [#164](https://github.com/o-stepper/graphorin/pull/164) [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00) Thanks [@o-stepper](https://github.com/o-stepper)! - Every published package now declares its tree-shaking contract via `sideEffects` (W-137): 18 packages audited to a pure module scope get `false`, the CLI declares its bin entry (`["./dist/bin/*"]`), and `@graphorin/security` gets an explicit `true` - its secrets subsystem registers built-in resolvers and the SecretValue caller-context provider at import time, so marking it pure would let bundlers drop those registrations. `mvp-readiness` now fails any publishable manifest without a declared `sideEffects`, closing the drift for future packages.
30
+
31
+ - Updated dependencies [[`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`832f22e`](https://github.com/o-stepper/graphorin/commit/832f22e570b8c3175c1adeb4c150070cbd131534), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`4ee256e`](https://github.com/o-stepper/graphorin/commit/4ee256e30fe9190cef6c48dc6785464757707156), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`32bbd03`](https://github.com/o-stepper/graphorin/commit/32bbd03b588136a355e4b5ad6ac5e19b36b4d8ab), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`764239b`](https://github.com/o-stepper/graphorin/commit/764239b97e0e0202442e91272583f13adeb12d00), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04), [`fe98522`](https://github.com/o-stepper/graphorin/commit/fe98522ce2477c9a7dc09029f9dcfdb1f7c9aa04)]:
32
+ - @graphorin/core@0.7.0
33
+ - @graphorin/observability@0.7.0
34
+
3
35
  ## 0.6.1
4
36
 
5
37
  ### Patch Changes
package/README.md CHANGED
@@ -106,5 +106,5 @@ recommendations.
106
106
 
107
107
  ## Project metadata
108
108
 
109
- - **Project Graphorin** · v0.6.1 · MIT License · © 2026 Oleksiy Stepurenko
109
+ - **Project Graphorin** · v0.7.0 · MIT License · © 2026 Oleksiy Stepurenko
110
110
  - Repository: <https://github.com/o-stepper/graphorin>
@@ -35,7 +35,7 @@ interface LlamaCppServerAdapterOptions {
35
35
  /**
36
36
  * Acknowledge the risk of running over plaintext HTTP against a
37
37
  * public host. Without this flag the adapter throws
38
- * {@link import('../errors/errors.js').LocalProviderInsecureTransportError}.
38
+ * `LocalProviderInsecureTransportError`.
39
39
  */
40
40
  readonly allowInsecureTransport?: boolean;
41
41
  /** Override for the default `acceptsSensitivity` value. */
@@ -1 +1 @@
1
- {"version":3,"file":"llamacpp-server.js","names":[],"sources":["../../src/adapters/llamacpp-server.ts"],"sourcesContent":["/**\n * Direct adapter for the upstream `llama-server` binary from the\n * llama.cpp project. The binary speaks the OpenAI-compatible REST\n * contract end-to-end (`POST /v1/chat/completions`, `POST /v1/completions`,\n * `POST /v1/embeddings`); streaming is via `text/event-stream` chunks\n * terminated by `data: [DONE]` exactly as the upstream OpenAI shape.\n *\n * The adapter shares a single `LocalProviderTrust` classifier with\n * `ollamaAdapter` and `openAICompatibleAdapter` - one classifier, one\n * policy table, one error type.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, Sensitivity } from '@graphorin/core';\n\nimport { buildOpenAIShapedProvider } from '../internal/openai-shaped.js';\n\n/**\n * Default port used by the upstream `llama-server` binary.\n *\n * @stable\n */\nexport const DEFAULT_LLAMACPP_SERVER_BASE_URL = 'http://127.0.0.1:8080';\n\n/**\n * Options accepted by {@link llamaCppServerAdapter}.\n *\n * @stable\n */\nexport interface LlamaCppServerAdapterOptions {\n /** GGUF model identifier exposed by the running server (e.g. `'qwen2.5:7b-instruct-q4_k_m'`). */\n readonly model: string;\n /** Base URL of the running `llama-server` process. Defaults to `http://127.0.0.1:8080`. */\n readonly baseUrl?: string;\n /** Optional bearer-auth API key (`--api-key` flag on the server). */\n readonly apiKey?: string;\n /** Extra headers merged on top of `content-type` + `accept` defaults. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Custom `fetch` implementation; useful for tests. */\n readonly fetchImpl?: typeof fetch;\n /**\n * Time-to-response budget per request (PS-24). Default\n * `DEFAULT_REQUEST_TIMEOUT_MS` (120s); `0` disables.\n */\n readonly timeoutMs?: number;\n /** Capability overrides merged on top of the adapter defaults. */\n readonly capabilities?: Partial<import('@graphorin/core').ProviderCapabilities>;\n /**\n * Acknowledge the risk of running over plaintext HTTP against a\n * public host. Without this flag the adapter throws\n * {@link import('../errors/errors.js').LocalProviderInsecureTransportError}.\n */\n readonly allowInsecureTransport?: boolean;\n /** Override for the default `acceptsSensitivity` value. */\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n /** Provider name attached to spans / log lines. */\n readonly name?: string;\n /** Optional log sink. Tests pass a fixture sink to silence the console. */\n readonly logger?: (level: 'warn' | 'info', message: string, meta?: object) => void;\n}\n\n/**\n * Build a Graphorin {@link Provider} backed by the upstream\n * `llama-server` binary. The factory does not start the binary -\n * operators launch it themselves with the desired model + GPU flags\n * and pass the URL here.\n *\n * @example\n * ```ts\n * const local = createProvider(\n * llamaCppServerAdapter({\n * model: 'qwen2.5:7b-instruct-q4_k_m',\n * baseUrl: 'http://127.0.0.1:8080',\n * }),\n * );\n * ```\n *\n * @stable\n */\nexport function llamaCppServerAdapter(options: LlamaCppServerAdapterOptions): Provider {\n const baseUrl = options.baseUrl ?? DEFAULT_LLAMACPP_SERVER_BASE_URL;\n const providerName = options.name ?? `llamacpp-server-${options.model}`;\n const built = buildOpenAIShapedProvider({\n providerName,\n model: options.model,\n baseUrl,\n ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n ...(options.headers !== undefined ? { headers: options.headers } : {}),\n ...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.allowInsecureTransport !== undefined\n ? { allowInsecureTransport: options.allowInsecureTransport }\n : {}),\n ...(options.acceptsSensitivity !== undefined\n ? { acceptsSensitivity: options.acceptsSensitivity }\n : {}),\n ...(options.logger !== undefined ? { logger: options.logger } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n capabilities: { multimodal: false, parallelToolCalls: false, ...options.capabilities },\n });\n return built.provider;\n}\n"],"mappings":";;;;;;;;AAuBA,MAAa,mCAAmC;;;;;;;;;;;;;;;;;;;AAyDhD,SAAgB,sBAAsB,SAAiD;CACrF,MAAM,UAAU,QAAQ,WAAW;AAmBnC,QAjBc,0BAA0B;EACtC,cAFmB,QAAQ,QAAQ,mBAAmB,QAAQ;EAG9D,OAAO,QAAQ;EACf;EACA,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;EAClE,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;EACrE,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,WAAW,GAAG,EAAE;EAC3E,GAAI,QAAQ,2BAA2B,SACnC,EAAE,wBAAwB,QAAQ,wBAAwB,GAC1D,EAAE;EACN,GAAI,QAAQ,uBAAuB,SAC/B,EAAE,oBAAoB,QAAQ,oBAAoB,GAClD,EAAE;EACN,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;EAClE,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,WAAW,GAAG,EAAE;EAC3E,cAAc;GAAE,YAAY;GAAO,mBAAmB;GAAO,GAAG,QAAQ;GAAc;EACvF,CAAC,CACW"}
1
+ {"version":3,"file":"llamacpp-server.js","names":[],"sources":["../../src/adapters/llamacpp-server.ts"],"sourcesContent":["/**\n * Direct adapter for the upstream `llama-server` binary from the\n * llama.cpp project. The binary speaks the OpenAI-compatible REST\n * contract end-to-end (`POST /v1/chat/completions`, `POST /v1/completions`,\n * `POST /v1/embeddings`); streaming is via `text/event-stream` chunks\n * terminated by `data: [DONE]` exactly as the upstream OpenAI shape.\n *\n * The adapter shares a single `LocalProviderTrust` classifier with\n * `ollamaAdapter` and `openAICompatibleAdapter` - one classifier, one\n * policy table, one error type.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, Sensitivity } from '@graphorin/core';\n\nimport { buildOpenAIShapedProvider } from '../internal/openai-shaped.js';\n\n/**\n * Default port used by the upstream `llama-server` binary.\n *\n * @stable\n */\nexport const DEFAULT_LLAMACPP_SERVER_BASE_URL = 'http://127.0.0.1:8080';\n\n/**\n * Options accepted by {@link llamaCppServerAdapter}.\n *\n * @stable\n */\nexport interface LlamaCppServerAdapterOptions {\n /** GGUF model identifier exposed by the running server (e.g. `'qwen2.5:7b-instruct-q4_k_m'`). */\n readonly model: string;\n /** Base URL of the running `llama-server` process. Defaults to `http://127.0.0.1:8080`. */\n readonly baseUrl?: string;\n /** Optional bearer-auth API key (`--api-key` flag on the server). */\n readonly apiKey?: string;\n /** Extra headers merged on top of `content-type` + `accept` defaults. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Custom `fetch` implementation; useful for tests. */\n readonly fetchImpl?: typeof fetch;\n /**\n * Time-to-response budget per request (PS-24). Default\n * `DEFAULT_REQUEST_TIMEOUT_MS` (120s); `0` disables.\n */\n readonly timeoutMs?: number;\n /** Capability overrides merged on top of the adapter defaults. */\n readonly capabilities?: Partial<import('@graphorin/core').ProviderCapabilities>;\n /**\n * Acknowledge the risk of running over plaintext HTTP against a\n * public host. Without this flag the adapter throws\n * `LocalProviderInsecureTransportError`.\n */\n readonly allowInsecureTransport?: boolean;\n /** Override for the default `acceptsSensitivity` value. */\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n /** Provider name attached to spans / log lines. */\n readonly name?: string;\n /** Optional log sink. Tests pass a fixture sink to silence the console. */\n readonly logger?: (level: 'warn' | 'info', message: string, meta?: object) => void;\n}\n\n/**\n * Build a Graphorin {@link Provider} backed by the upstream\n * `llama-server` binary. The factory does not start the binary -\n * operators launch it themselves with the desired model + GPU flags\n * and pass the URL here.\n *\n * @example\n * ```ts\n * const local = createProvider(\n * llamaCppServerAdapter({\n * model: 'qwen2.5:7b-instruct-q4_k_m',\n * baseUrl: 'http://127.0.0.1:8080',\n * }),\n * );\n * ```\n *\n * @stable\n */\nexport function llamaCppServerAdapter(options: LlamaCppServerAdapterOptions): Provider {\n const baseUrl = options.baseUrl ?? DEFAULT_LLAMACPP_SERVER_BASE_URL;\n const providerName = options.name ?? `llamacpp-server-${options.model}`;\n const built = buildOpenAIShapedProvider({\n providerName,\n model: options.model,\n baseUrl,\n ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n ...(options.headers !== undefined ? { headers: options.headers } : {}),\n ...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.allowInsecureTransport !== undefined\n ? { allowInsecureTransport: options.allowInsecureTransport }\n : {}),\n ...(options.acceptsSensitivity !== undefined\n ? { acceptsSensitivity: options.acceptsSensitivity }\n : {}),\n ...(options.logger !== undefined ? { logger: options.logger } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n capabilities: { multimodal: false, parallelToolCalls: false, ...options.capabilities },\n });\n return built.provider;\n}\n"],"mappings":";;;;;;;;AAuBA,MAAa,mCAAmC;;;;;;;;;;;;;;;;;;;AAyDhD,SAAgB,sBAAsB,SAAiD;CACrF,MAAM,UAAU,QAAQ,WAAW;AAmBnC,QAjBc,0BAA0B;EACtC,cAFmB,QAAQ,QAAQ,mBAAmB,QAAQ;EAG9D,OAAO,QAAQ;EACf;EACA,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;EAClE,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,SAAS,GAAG,EAAE;EACrE,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,WAAW,GAAG,EAAE;EAC3E,GAAI,QAAQ,2BAA2B,SACnC,EAAE,wBAAwB,QAAQ,wBAAwB,GAC1D,EAAE;EACN,GAAI,QAAQ,uBAAuB,SAC/B,EAAE,oBAAoB,QAAQ,oBAAoB,GAClD,EAAE;EACN,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;EAClE,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,WAAW,GAAG,EAAE;EAC3E,cAAc;GAAE,YAAY;GAAO,mBAAmB;GAAO,GAAG,QAAQ;GAAc;EACvF,CAAC,CACW"}
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.d.ts","names":[],"sources":["../../src/adapters/ollama.ts"],"sourcesContent":[],"mappings":";;;;;AAqFA;;;;cA5Ca,uBAAA;;;;;;UAOI,oBAAA;;;;qBAII,SAAS;8BACA;;;;;;;gCAOE,cAAc;0BACpB,QAAQ;;;;;;;;;;;iBAwBlB,aAAA,UAAuB,uBAAuB"}
1
+ {"version":3,"file":"ollama.d.ts","names":[],"sources":["../../src/adapters/ollama.ts"],"sourcesContent":[],"mappings":";;;;;AA0FA;;;;cA5Ca,uBAAA;;;;;;UAOI,oBAAA;;;;qBAII,SAAS;8BACA;;;;;;;gCAOE,cAAc;0BACpB,QAAQ;;;;;;;;;;;iBAwBlB,aAAA,UAAuB,uBAAuB"}
@@ -14,8 +14,8 @@ import { randomUUID } from "node:crypto";
14
14
  * native Ollama streaming JSON protocol (`POST /api/chat` returning
15
15
  * newline-delimited JSON objects). For operators who prefer the
16
16
  * OpenAI-compatible variant exposed by recent Ollama releases, the
17
- * generic {@link openAICompatibleAdapter} is the better choice - both
18
- * adapters share the same {@link LocalProviderTrust} classifier and
17
+ * generic `openAICompatibleAdapter` is the better choice - both
18
+ * adapters share the same `LocalProviderTrust` classifier and
19
19
  * {@link LocalProviderInsecureTransportError} startup behaviour.
20
20
  *
21
21
  * @packageDocumentation
@@ -87,7 +87,7 @@ function applyOllamaPreflight(req, capabilities) {
87
87
  };
88
88
  }
89
89
  async function* streamOllama(options, providerName, url, req) {
90
- const body = buildBody(options.model, req, true, options.capabilities?.structuredOutput ?? true);
90
+ const body = buildBody(options.model, req, true, options.capabilities?.structuredOutput ?? true, conversionOptionsFor(options));
91
91
  const resp = await callJsonHttp({
92
92
  providerName,
93
93
  url,
@@ -150,7 +150,7 @@ async function* streamOllama(options, providerName, url, req) {
150
150
  };
151
151
  }
152
152
  async function generateOllama(options, providerName, url, req) {
153
- const body = buildBody(options.model, req, false, options.capabilities?.structuredOutput ?? true);
153
+ const body = buildBody(options.model, req, false, options.capabilities?.structuredOutput ?? true, conversionOptionsFor(options));
154
154
  const resp = await callJsonHttp({
155
155
  providerName,
156
156
  url,
@@ -177,13 +177,13 @@ async function generateOllama(options, providerName, url, req) {
177
177
  })) } : {}
178
178
  };
179
179
  }
180
- function buildBody(model, req, stream, structuredOutput) {
180
+ function buildBody(model, req, stream, structuredOutput, conversion) {
181
181
  const body = {
182
182
  model,
183
183
  messages: toOllamaChatMessages(req.systemMessage !== void 0 ? [{
184
184
  role: "system",
185
185
  content: req.systemMessage
186
- }, ...req.messages] : req.messages),
186
+ }, ...req.messages] : req.messages, conversion),
187
187
  stream
188
188
  };
189
189
  if (req.temperature !== void 0 || req.maxTokens !== void 0) {
@@ -241,6 +241,19 @@ function defaultLogger(level, message, meta) {
241
241
  if (meta !== void 0) fn(`[graphorin/provider] ${message}`, meta);
242
242
  else fn(`[graphorin/provider] ${message}`);
243
243
  }
244
+ /** W-095: one dropped-content WARN per adapter instance. */
245
+ const droppedContentWarned = /* @__PURE__ */ new WeakSet();
246
+ function conversionOptionsFor(options) {
247
+ const log = options.logger ?? defaultLogger;
248
+ return {
249
+ multimodal: options.capabilities?.multimodal ?? false,
250
+ warn: (message) => {
251
+ if (droppedContentWarned.has(options)) return;
252
+ droppedContentWarned.add(options);
253
+ log("warn", message);
254
+ }
255
+ };
256
+ }
244
257
 
245
258
  //#endregion
246
259
  export { DEFAULT_OLLAMA_BASE_URL, ollamaAdapter };
@@ -1 +1 @@
1
- {"version":3,"file":"ollama.js","names":["DEFAULT_CAPABILITIES: ProviderCapabilities","capabilities: ProviderCapabilities","usage: Usage","finishReason: FinishReason","chunk: OllamaChatChunk","json: OllamaChatChunk","body: Record<string, unknown>","optionsBlock: Record<string, unknown>"],"sources":["../../src/adapters/ollama.ts"],"sourcesContent":["/**\n * Direct adapter for the Ollama HTTP API. The adapter speaks the\n * native Ollama streaming JSON protocol (`POST /api/chat` returning\n * newline-delimited JSON objects). For operators who prefer the\n * OpenAI-compatible variant exposed by recent Ollama releases, the\n * generic {@link openAICompatibleAdapter} is the better choice - both\n * adapters share the same {@link LocalProviderTrust} classifier and\n * {@link LocalProviderInsecureTransportError} startup behaviour.\n *\n * @packageDocumentation\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n FinishReason,\n Provider,\n ProviderCapabilities,\n ProviderEvent,\n ProviderRequest,\n ProviderResponse,\n Sensitivity,\n Usage,\n} from '@graphorin/core';\n\nimport { LocalProviderInsecureTransportError, ProviderStreamParseError } from '../errors/errors.js';\nimport { callJsonHttp, makeStreamStartEvent, toOllamaChatMessages } from '../internal/http.js';\nimport { parseNdJsonStream } from '../internal/sse.js';\nimport { stripTrailingSlashes } from '../internal/url-utils.js';\nimport { applyReasoningPolicy } from '../reasoning/apply-policy.js';\nimport { resolveReasoningRetention } from '../reasoning/retention.js';\nimport { foldToolExamples } from '../tool-examples.js';\nimport {\n classifyLocalProvider,\n type LocalProviderClassification,\n} from '../trust/classify-local-provider.js';\n\n/**\n * Default Ollama base URL.\n *\n * @stable\n */\nexport const DEFAULT_OLLAMA_BASE_URL = 'http://127.0.0.1:11434';\n\n/**\n * Options accepted by {@link ollamaAdapter}.\n *\n * @stable\n */\nexport interface OllamaAdapterOptions {\n readonly model: string;\n readonly baseUrl?: string;\n readonly chatPath?: string;\n readonly headers?: Readonly<Record<string, string>>;\n readonly fetchImpl?: typeof fetch;\n /**\n * Time-to-response budget per request (PS-24). Default\n * `DEFAULT_REQUEST_TIMEOUT_MS` (120s); `0` disables.\n */\n readonly timeoutMs?: number;\n readonly allowInsecureTransport?: boolean;\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n readonly capabilities?: Partial<ProviderCapabilities>;\n readonly name?: string;\n readonly logger?: (level: 'warn' | 'info', message: string, meta?: object) => void;\n}\n\nconst DEFAULT_CAPABILITIES: ProviderCapabilities = {\n streaming: true,\n toolCalling: true,\n parallelToolCalls: false,\n multimodal: false,\n structuredOutput: true,\n reasoning: false,\n contextWindow: 8_192,\n maxOutput: 4_096,\n reasoningContract: 'optional',\n};\n\n/**\n * Build a Graphorin {@link Provider} backed by Ollama's native HTTP\n * API. The adapter is fail-safe by default: public-cleartext URLs\n * refuse to start with `LocalProviderInsecureTransportError`.\n *\n * @stable\n */\nexport function ollamaAdapter(options: OllamaAdapterOptions): Provider {\n const baseUrl = options.baseUrl ?? DEFAULT_OLLAMA_BASE_URL;\n const classification = classifyLocalProvider(baseUrl);\n if (classification.trust === 'public-cleartext' && options.allowInsecureTransport !== true) {\n throw new LocalProviderInsecureTransportError(baseUrl);\n }\n const log = options.logger ?? defaultLogger;\n emitTrustWarning(log, classification, baseUrl);\n const acceptsSensitivity = options.acceptsSensitivity ?? classification.acceptsSensitivity;\n const providerName = options.name ?? `ollama-${options.model}`;\n const capabilities: ProviderCapabilities = {\n ...DEFAULT_CAPABILITIES,\n ...options.capabilities,\n };\n const chatPath = options.chatPath ?? '/api/chat';\n const url = `${stripTrailingSlashes(baseUrl)}${chatPath}`;\n return {\n name: providerName,\n modelId: options.model,\n capabilities,\n acceptsSensitivity,\n stream(req) {\n return streamOllama(options, providerName, url, applyOllamaPreflight(req, capabilities));\n },\n async generate(req) {\n return generateOllama(options, providerName, url, applyOllamaPreflight(req, capabilities));\n },\n };\n}\n\nfunction applyOllamaPreflight(\n req: ProviderRequest,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n const retention = resolveReasoningRetention({\n ...(req.reasoningRetention !== undefined ? { requested: req.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (retention === 'pass-through-all') return req;\n const filtered = applyReasoningPolicy({ messages: req.messages, retention });\n if (filtered === req.messages) return req;\n return { ...req, messages: filtered };\n}\n\nasync function* streamOllama(\n options: OllamaAdapterOptions,\n providerName: string,\n url: string,\n req: ProviderRequest,\n): AsyncIterable<ProviderEvent> {\n const body = buildBody(options.model, req, true, options.capabilities?.structuredOutput ?? true);\n const resp = await callJsonHttp({\n providerName,\n url,\n headers: buildHeaders(options),\n body,\n ...(req.signal !== undefined ? { signal: req.signal } : {}),\n ...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n yield makeStreamStartEvent({ providerName, modelId: options.model });\n let usage: Usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n let finishReason: FinishReason = 'stop';\n for await (const line of parseNdJsonStream(\n resp.body,\n req.signal !== undefined ? { signal: req.signal } : {},\n )) {\n if (req.signal?.aborted) {\n finishReason = 'aborted'; // PS-12: honest abort reason, not 'stop'\n break;\n }\n let chunk: OllamaChatChunk;\n try {\n chunk = JSON.parse(line) as OllamaChatChunk;\n } catch (cause) {\n throw new ProviderStreamParseError(\n providerName,\n `failed to parse ndjson line: ${(cause as Error).message}`,\n cause,\n );\n }\n if (chunk.message?.content !== undefined && chunk.message.content.length > 0) {\n yield { type: 'text-delta', delta: chunk.message.content };\n }\n if (Array.isArray(chunk.message?.tool_calls)) {\n for (const tc of chunk.message.tool_calls) {\n const toolCallId = tc.id ?? `call_${randomUUID()}`;\n const toolName = tc.function?.name ?? '';\n if (toolName.length === 0) continue;\n yield { type: 'tool-call-start', toolCallId, toolName };\n yield {\n type: 'tool-call-end',\n toolCallId,\n finalArgs: tc.function?.arguments ?? {},\n };\n }\n }\n if (chunk.done === true) {\n finishReason = mapFinishReason(chunk.done_reason);\n usage = mapUsage(chunk);\n break;\n }\n }\n yield { type: 'finish', finishReason, usage };\n}\n\nasync function generateOllama(\n options: OllamaAdapterOptions,\n providerName: string,\n url: string,\n req: ProviderRequest,\n): Promise<ProviderResponse> {\n const body = buildBody(options.model, req, false, options.capabilities?.structuredOutput ?? true);\n const resp = await callJsonHttp({\n providerName,\n url,\n headers: buildHeaders(options),\n body,\n ...(req.signal !== undefined ? { signal: req.signal } : {}),\n ...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n let json: OllamaChatChunk;\n try {\n json = (await resp.json()) as OllamaChatChunk;\n } catch (cause) {\n throw new ProviderStreamParseError(providerName, 'response body was not valid JSON', cause);\n }\n return {\n usage: mapUsage(json),\n finishReason: mapFinishReason(json.done_reason),\n ...(typeof json.message?.content === 'string' && json.message.content.length > 0\n ? { text: json.message.content }\n : {}),\n ...(Array.isArray(json.message?.tool_calls) && json.message.tool_calls.length > 0\n ? {\n toolCalls: json.message.tool_calls.map((tc) => ({\n toolCallId: tc.id ?? `call_${randomUUID()}`,\n toolName: tc.function?.name ?? '',\n args: tc.function?.arguments ?? {},\n })),\n }\n : {}),\n };\n}\n\nfunction buildBody(\n model: string,\n req: ProviderRequest,\n stream: boolean,\n structuredOutput: boolean,\n): Record<string, unknown> {\n const messages =\n req.systemMessage !== undefined\n ? [{ role: 'system' as const, content: req.systemMessage }, ...req.messages]\n : req.messages;\n const body: Record<string, unknown> = {\n model,\n messages: toOllamaChatMessages(messages),\n stream,\n };\n if (req.temperature !== undefined || req.maxTokens !== undefined) {\n const optionsBlock: Record<string, unknown> = {};\n if (req.temperature !== undefined) optionsBlock.temperature = req.temperature;\n if (req.maxTokens !== undefined) optionsBlock.num_predict = req.maxTokens;\n body.options = optionsBlock;\n }\n if (req.tools !== undefined && req.tools.length > 0) {\n // C2: fold worked examples in the adapter itself (idempotent when an\n // upstream createProvider fold already ran).\n body.tools = foldToolExamples(req.tools).map((t) => ({\n type: 'function',\n function: {\n name: t.name,\n ...(t.description !== undefined ? { description: t.description } : {}),\n parameters: t.inputSchema,\n },\n }));\n }\n // PS-24: Ollama's native structured output - `format` takes a JSON\n // schema object (or 'json' for schema-less JSON mode).\n if (structuredOutput && req.outputType?.kind === 'structured') {\n body.format = req.outputType.jsonSchema ?? 'json';\n }\n if (req.providerOptions !== undefined) {\n Object.assign(body, req.providerOptions);\n }\n return body;\n}\n\nfunction buildHeaders(options: OllamaAdapterOptions): Record<string, string> {\n return {\n 'content-type': 'application/json',\n accept: 'application/json',\n ...options.headers,\n };\n}\n\nfunction mapFinishReason(value: string | null | undefined): FinishReason {\n switch (value) {\n case 'stop':\n case 'length':\n return value;\n case 'tool_calls':\n return 'tool-calls';\n default:\n return 'stop';\n }\n}\n\nfunction mapUsage(chunk: OllamaChatChunk): Usage {\n const promptTokens = chunk.prompt_eval_count ?? 0;\n const completionTokens = chunk.eval_count ?? 0;\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n };\n}\n\nfunction emitTrustWarning(\n log: (level: 'warn' | 'info', message: string, meta?: object) => void,\n classification: LocalProviderClassification,\n baseUrl: string,\n): void {\n if (classification.trust === 'public-cleartext') {\n log('warn', `[ollama] allowInsecureTransport=true accepted for ${baseUrl}`, { baseUrl });\n } else if (classification.trust === 'public-tls') {\n log('warn', `[ollama] public-TLS endpoint; treating as cloud-tier`, { baseUrl });\n } else if (classification.trust === 'private') {\n log('warn', `[ollama] private-network endpoint detected (${classification.reason})`, {\n baseUrl,\n acceptsSensitivity: classification.acceptsSensitivity,\n });\n }\n}\n\nfunction defaultLogger(level: 'warn' | 'info', message: string, meta?: object): void {\n const fn = level === 'warn' ? console.warn : console.info;\n if (meta !== undefined) {\n fn(`[graphorin/provider] ${message}`, meta);\n } else {\n fn(`[graphorin/provider] ${message}`);\n }\n}\n\ninterface OllamaChatChunk {\n readonly model?: string;\n readonly created_at?: string;\n readonly message?: {\n readonly role?: string;\n readonly content?: string;\n readonly tool_calls?: ReadonlyArray<{\n readonly id?: string;\n readonly function?: { readonly name?: string; readonly arguments?: unknown };\n }>;\n };\n readonly done?: boolean;\n readonly done_reason?: string;\n readonly prompt_eval_count?: number;\n readonly eval_count?: number;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ollama.js","names":["DEFAULT_CAPABILITIES: ProviderCapabilities","capabilities: ProviderCapabilities","usage: Usage","finishReason: FinishReason","chunk: OllamaChatChunk","json: OllamaChatChunk","body: Record<string, unknown>","optionsBlock: Record<string, unknown>"],"sources":["../../src/adapters/ollama.ts"],"sourcesContent":["/**\n * Direct adapter for the Ollama HTTP API. The adapter speaks the\n * native Ollama streaming JSON protocol (`POST /api/chat` returning\n * newline-delimited JSON objects). For operators who prefer the\n * OpenAI-compatible variant exposed by recent Ollama releases, the\n * generic `openAICompatibleAdapter` is the better choice - both\n * adapters share the same `LocalProviderTrust` classifier and\n * {@link LocalProviderInsecureTransportError} startup behaviour.\n *\n * @packageDocumentation\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n FinishReason,\n Provider,\n ProviderCapabilities,\n ProviderEvent,\n ProviderRequest,\n ProviderResponse,\n Sensitivity,\n Usage,\n} from '@graphorin/core';\n\nimport { LocalProviderInsecureTransportError, ProviderStreamParseError } from '../errors/errors.js';\nimport {\n type ChatMessageConversionOptions,\n callJsonHttp,\n makeStreamStartEvent,\n toOllamaChatMessages,\n} from '../internal/http.js';\nimport { parseNdJsonStream } from '../internal/sse.js';\nimport { stripTrailingSlashes } from '../internal/url-utils.js';\nimport { applyReasoningPolicy } from '../reasoning/apply-policy.js';\nimport { resolveReasoningRetention } from '../reasoning/retention.js';\nimport { foldToolExamples } from '../tool-examples.js';\nimport {\n classifyLocalProvider,\n type LocalProviderClassification,\n} from '../trust/classify-local-provider.js';\n\n/**\n * Default Ollama base URL.\n *\n * @stable\n */\nexport const DEFAULT_OLLAMA_BASE_URL = 'http://127.0.0.1:11434';\n\n/**\n * Options accepted by {@link ollamaAdapter}.\n *\n * @stable\n */\nexport interface OllamaAdapterOptions {\n readonly model: string;\n readonly baseUrl?: string;\n readonly chatPath?: string;\n readonly headers?: Readonly<Record<string, string>>;\n readonly fetchImpl?: typeof fetch;\n /**\n * Time-to-response budget per request (PS-24). Default\n * `DEFAULT_REQUEST_TIMEOUT_MS` (120s); `0` disables.\n */\n readonly timeoutMs?: number;\n readonly allowInsecureTransport?: boolean;\n readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;\n readonly capabilities?: Partial<ProviderCapabilities>;\n readonly name?: string;\n readonly logger?: (level: 'warn' | 'info', message: string, meta?: object) => void;\n}\n\nconst DEFAULT_CAPABILITIES: ProviderCapabilities = {\n streaming: true,\n toolCalling: true,\n parallelToolCalls: false,\n multimodal: false,\n structuredOutput: true,\n reasoning: false,\n contextWindow: 8_192,\n maxOutput: 4_096,\n reasoningContract: 'optional',\n};\n\n/**\n * Build a Graphorin {@link Provider} backed by Ollama's native HTTP\n * API. The adapter is fail-safe by default: public-cleartext URLs\n * refuse to start with `LocalProviderInsecureTransportError`.\n *\n * @stable\n */\nexport function ollamaAdapter(options: OllamaAdapterOptions): Provider {\n const baseUrl = options.baseUrl ?? DEFAULT_OLLAMA_BASE_URL;\n const classification = classifyLocalProvider(baseUrl);\n if (classification.trust === 'public-cleartext' && options.allowInsecureTransport !== true) {\n throw new LocalProviderInsecureTransportError(baseUrl);\n }\n const log = options.logger ?? defaultLogger;\n emitTrustWarning(log, classification, baseUrl);\n const acceptsSensitivity = options.acceptsSensitivity ?? classification.acceptsSensitivity;\n const providerName = options.name ?? `ollama-${options.model}`;\n const capabilities: ProviderCapabilities = {\n ...DEFAULT_CAPABILITIES,\n ...options.capabilities,\n };\n const chatPath = options.chatPath ?? '/api/chat';\n const url = `${stripTrailingSlashes(baseUrl)}${chatPath}`;\n return {\n name: providerName,\n modelId: options.model,\n capabilities,\n acceptsSensitivity,\n stream(req) {\n return streamOllama(options, providerName, url, applyOllamaPreflight(req, capabilities));\n },\n async generate(req) {\n return generateOllama(options, providerName, url, applyOllamaPreflight(req, capabilities));\n },\n };\n}\n\nfunction applyOllamaPreflight(\n req: ProviderRequest,\n capabilities: ProviderCapabilities,\n): ProviderRequest {\n const retention = resolveReasoningRetention({\n ...(req.reasoningRetention !== undefined ? { requested: req.reasoningRetention } : {}),\n ...(capabilities.reasoningContract !== undefined\n ? { contract: capabilities.reasoningContract }\n : {}),\n });\n if (retention === 'pass-through-all') return req;\n const filtered = applyReasoningPolicy({ messages: req.messages, retention });\n if (filtered === req.messages) return req;\n return { ...req, messages: filtered };\n}\n\nasync function* streamOllama(\n options: OllamaAdapterOptions,\n providerName: string,\n url: string,\n req: ProviderRequest,\n): AsyncIterable<ProviderEvent> {\n const body = buildBody(\n options.model,\n req,\n true,\n options.capabilities?.structuredOutput ?? true,\n conversionOptionsFor(options),\n );\n const resp = await callJsonHttp({\n providerName,\n url,\n headers: buildHeaders(options),\n body,\n ...(req.signal !== undefined ? { signal: req.signal } : {}),\n ...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n yield makeStreamStartEvent({ providerName, modelId: options.model });\n let usage: Usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };\n let finishReason: FinishReason = 'stop';\n for await (const line of parseNdJsonStream(\n resp.body,\n req.signal !== undefined ? { signal: req.signal } : {},\n )) {\n if (req.signal?.aborted) {\n finishReason = 'aborted'; // PS-12: honest abort reason, not 'stop'\n break;\n }\n let chunk: OllamaChatChunk;\n try {\n chunk = JSON.parse(line) as OllamaChatChunk;\n } catch (cause) {\n throw new ProviderStreamParseError(\n providerName,\n `failed to parse ndjson line: ${(cause as Error).message}`,\n cause,\n );\n }\n if (chunk.message?.content !== undefined && chunk.message.content.length > 0) {\n yield { type: 'text-delta', delta: chunk.message.content };\n }\n if (Array.isArray(chunk.message?.tool_calls)) {\n for (const tc of chunk.message.tool_calls) {\n const toolCallId = tc.id ?? `call_${randomUUID()}`;\n const toolName = tc.function?.name ?? '';\n if (toolName.length === 0) continue;\n yield { type: 'tool-call-start', toolCallId, toolName };\n yield {\n type: 'tool-call-end',\n toolCallId,\n finalArgs: tc.function?.arguments ?? {},\n };\n }\n }\n if (chunk.done === true) {\n finishReason = mapFinishReason(chunk.done_reason);\n usage = mapUsage(chunk);\n break;\n }\n }\n yield { type: 'finish', finishReason, usage };\n}\n\nasync function generateOllama(\n options: OllamaAdapterOptions,\n providerName: string,\n url: string,\n req: ProviderRequest,\n): Promise<ProviderResponse> {\n const body = buildBody(\n options.model,\n req,\n false,\n options.capabilities?.structuredOutput ?? true,\n conversionOptionsFor(options),\n );\n const resp = await callJsonHttp({\n providerName,\n url,\n headers: buildHeaders(options),\n body,\n ...(req.signal !== undefined ? { signal: req.signal } : {}),\n ...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n });\n let json: OllamaChatChunk;\n try {\n json = (await resp.json()) as OllamaChatChunk;\n } catch (cause) {\n throw new ProviderStreamParseError(providerName, 'response body was not valid JSON', cause);\n }\n return {\n usage: mapUsage(json),\n finishReason: mapFinishReason(json.done_reason),\n ...(typeof json.message?.content === 'string' && json.message.content.length > 0\n ? { text: json.message.content }\n : {}),\n ...(Array.isArray(json.message?.tool_calls) && json.message.tool_calls.length > 0\n ? {\n toolCalls: json.message.tool_calls.map((tc) => ({\n toolCallId: tc.id ?? `call_${randomUUID()}`,\n toolName: tc.function?.name ?? '',\n args: tc.function?.arguments ?? {},\n })),\n }\n : {}),\n };\n}\n\nfunction buildBody(\n model: string,\n req: ProviderRequest,\n stream: boolean,\n structuredOutput: boolean,\n conversion: ChatMessageConversionOptions,\n): Record<string, unknown> {\n const messages =\n req.systemMessage !== undefined\n ? [{ role: 'system' as const, content: req.systemMessage }, ...req.messages]\n : req.messages;\n const body: Record<string, unknown> = {\n model,\n messages: toOllamaChatMessages(messages, conversion),\n stream,\n };\n if (req.temperature !== undefined || req.maxTokens !== undefined) {\n const optionsBlock: Record<string, unknown> = {};\n if (req.temperature !== undefined) optionsBlock.temperature = req.temperature;\n if (req.maxTokens !== undefined) optionsBlock.num_predict = req.maxTokens;\n body.options = optionsBlock;\n }\n if (req.tools !== undefined && req.tools.length > 0) {\n // C2: fold worked examples in the adapter itself (idempotent when an\n // upstream createProvider fold already ran).\n body.tools = foldToolExamples(req.tools).map((t) => ({\n type: 'function',\n function: {\n name: t.name,\n ...(t.description !== undefined ? { description: t.description } : {}),\n parameters: t.inputSchema,\n },\n }));\n }\n // PS-24: Ollama's native structured output - `format` takes a JSON\n // schema object (or 'json' for schema-less JSON mode).\n if (structuredOutput && req.outputType?.kind === 'structured') {\n body.format = req.outputType.jsonSchema ?? 'json';\n }\n if (req.providerOptions !== undefined) {\n Object.assign(body, req.providerOptions);\n }\n return body;\n}\n\nfunction buildHeaders(options: OllamaAdapterOptions): Record<string, string> {\n return {\n 'content-type': 'application/json',\n accept: 'application/json',\n ...options.headers,\n };\n}\n\nfunction mapFinishReason(value: string | null | undefined): FinishReason {\n switch (value) {\n case 'stop':\n case 'length':\n return value;\n case 'tool_calls':\n return 'tool-calls';\n default:\n return 'stop';\n }\n}\n\nfunction mapUsage(chunk: OllamaChatChunk): Usage {\n const promptTokens = chunk.prompt_eval_count ?? 0;\n const completionTokens = chunk.eval_count ?? 0;\n return {\n promptTokens,\n completionTokens,\n totalTokens: promptTokens + completionTokens,\n };\n}\n\nfunction emitTrustWarning(\n log: (level: 'warn' | 'info', message: string, meta?: object) => void,\n classification: LocalProviderClassification,\n baseUrl: string,\n): void {\n if (classification.trust === 'public-cleartext') {\n log('warn', `[ollama] allowInsecureTransport=true accepted for ${baseUrl}`, { baseUrl });\n } else if (classification.trust === 'public-tls') {\n log('warn', `[ollama] public-TLS endpoint; treating as cloud-tier`, { baseUrl });\n } else if (classification.trust === 'private') {\n log('warn', `[ollama] private-network endpoint detected (${classification.reason})`, {\n baseUrl,\n acceptsSensitivity: classification.acceptsSensitivity,\n });\n }\n}\n\nfunction defaultLogger(level: 'warn' | 'info', message: string, meta?: object): void {\n const fn = level === 'warn' ? console.warn : console.info;\n if (meta !== undefined) {\n fn(`[graphorin/provider] ${message}`, meta);\n } else {\n fn(`[graphorin/provider] ${message}`);\n }\n}\n\n/** W-095: one dropped-content WARN per adapter instance. */\nconst droppedContentWarned = new WeakSet<object>();\n\nfunction conversionOptionsFor(options: OllamaAdapterOptions): ChatMessageConversionOptions {\n const log = options.logger ?? defaultLogger;\n return {\n multimodal: options.capabilities?.multimodal ?? false,\n warn: (message) => {\n if (droppedContentWarned.has(options)) return;\n droppedContentWarned.add(options);\n log('warn', message);\n },\n };\n}\n\ninterface OllamaChatChunk {\n readonly model?: string;\n readonly created_at?: string;\n readonly message?: {\n readonly role?: string;\n readonly content?: string;\n readonly tool_calls?: ReadonlyArray<{\n readonly id?: string;\n readonly function?: { readonly name?: string; readonly arguments?: unknown };\n }>;\n };\n readonly done?: boolean;\n readonly done_reason?: string;\n readonly prompt_eval_count?: number;\n readonly eval_count?: number;\n}\n"],"mappings":"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"}
@@ -115,7 +115,7 @@ interface VercelAdapterOptions {
115
115
  * the AI SDK are translated back onto Graphorin `ProviderEvent`s.
116
116
  *
117
117
  * The adapter auto-detects the model's
118
- * {@link import('@graphorin/core').ReasoningContract} from its
118
+ * `ReasoningContract` from its
119
119
  * `modelId` (e.g. Anthropic Claude → `'round-trip-required'`,
120
120
  * OpenAI o1 / o3 → `'hidden'`, Gemini reasoning variants →
121
121
  * `'hidden'`, everything else → `'optional'`). Callers can override
@@ -1 +1 @@
1
- {"version":3,"file":"vercel.d.ts","names":[],"sources":["../../src/adapters/vercel.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAwIA;;;AAiB8B,UAnGb,iBAAA,CAmGa;EAAsB,SAAA,QAAA,EAAA,MAAA;EA8BpC,SAAA,OAAa,EAAA,MAAA;EACpB,SAAA,oBAAA,CAAA,EAAA,MAAA,GAAA,MAAA;EACE;;;AAoaX;;;;;;;;;;;;;;;;UA/gBiB,UAAA;;;;;;;;;UAUA,sBAAA;;WAEN;;cAEG,cAAc,SAAS;;;;;;kBAMnB;sBACI,SAAS;;yBAEN,cAAc;;;WAG5B;;cAEG,cAAc,SAAS;;;;;;kBAMnB;sBACI,SAAS;QACvB;;yBAEiB;;;;;qBAKJ,QAAQ;;;;;;gCAMG,SAAS;;;;;;;;UASxB,oBAAA;;;;;;;;;;;0BAWS,QAAQ;;;;;;8BAMJ;;;;;;;;;;;;;;;;;;iBA8Bd,aAAA,QACP,6BACE,uBACR;;;;;;;;iBAmaa,yBAAA,CAAA"}
1
+ {"version":3,"file":"vercel.d.ts","names":[],"sources":["../../src/adapters/vercel.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA6IA;;;AAiB8B,UAnGb,iBAAA,CAmGa;EAAsB,SAAA,QAAA,EAAA,MAAA;EA8BpC,SAAA,OAAa,EAAA,MAAA;EACpB,SAAA,oBAAA,CAAA,EAAA,MAAA,GAAA,MAAA;EACE;;;AAigBX;;;;;;;;;;;;;;;;UA5mBiB,UAAA;;;;;;;;;UAUA,sBAAA;;WAEN;;cAEG,cAAc,SAAS;;;;;;kBAMnB;sBACI,SAAS;;yBAEN,cAAc;;;WAG5B;;cAEG,cAAc,SAAS;;;;;;kBAMnB;sBACI,SAAS;QACvB;;yBAEiB;;;;;qBAKJ,QAAQ;;;;;;gCAMG,SAAS;;;;;;;;UASxB,oBAAA;;;;;;;;;;;0BAWS,QAAQ;;;;;;8BAMJ;;;;;;;;;;;;;;;;;;iBA8Bd,aAAA,QACP,6BACE,uBACR;;;;;;;;iBAggBa,yBAAA,CAAA"}
@@ -1,7 +1,8 @@
1
- import { ProviderHttpError, ProviderStreamParseError } from "../errors/errors.js";
1
+ import { ProviderHttpError, ProviderStreamParseError, classifyHttpStatus } from "../errors/errors.js";
2
2
  import { applyReasoningPolicy } from "../reasoning/apply-policy.js";
3
3
  import { resolveReasoningRetention } from "../reasoning/retention.js";
4
4
  import { foldToolExamples } from "../tool-examples.js";
5
+ import { isAbortError } from "../internal/abort.js";
5
6
  import { inferReasoningContract } from "../reasoning/classify-contract.js";
6
7
  import { applyCacheAnchors, toAiSdkPrompt, toAiSdkToolChoice, toAiSdkTools } from "./vercel-messages.js";
7
8
 
@@ -24,7 +25,7 @@ const DEFAULT_CAPABILITIES = {
24
25
  * the AI SDK are translated back onto Graphorin `ProviderEvent`s.
25
26
  *
26
27
  * The adapter auto-detects the model's
27
- * {@link import('@graphorin/core').ReasoningContract} from its
28
+ * `ReasoningContract` from its
28
29
  * `modelId` (e.g. Anthropic Claude → `'round-trip-required'`,
29
30
  * OpenAI o1 / o3 → `'hidden'`, Gemini reasoning variants →
30
31
  * `'hidden'`, everything else → `'optional'`). Callers can override
@@ -72,15 +73,20 @@ async function* streamFromVercel(model, providerName, capabilities, req, overrid
72
73
  ...headers !== void 0 ? { headers } : {}
73
74
  });
74
75
  }
75
- yield {
76
- type: "stream-start",
77
- metadata: {
78
- providerName,
79
- modelId: model.modelId
80
- }
76
+ let emittedStart = false;
77
+ const startEvent = () => {
78
+ emittedStart = true;
79
+ return {
80
+ type: "stream-start",
81
+ metadata: {
82
+ providerName,
83
+ modelId: model.modelId
84
+ }
85
+ };
81
86
  };
82
87
  let finalUsage;
83
88
  let finishReason = "stop";
89
+ let sawError = false;
84
90
  for await (const chunk of stream) {
85
91
  if (req.signal?.aborted) {
86
92
  finishReason = "aborted";
@@ -89,18 +95,63 @@ async function* streamFromVercel(model, providerName, capabilities, req, overrid
89
95
  switch (chunk.type) {
90
96
  case "text-delta": {
91
97
  const delta = pickString(chunk.textDelta) ?? pickString(chunk.text) ?? "";
92
- if (delta.length > 0) yield {
93
- type: "text-delta",
94
- delta
95
- };
98
+ if (delta.length > 0) {
99
+ if (!emittedStart) yield startEvent();
100
+ yield {
101
+ type: "text-delta",
102
+ delta
103
+ };
104
+ }
96
105
  break;
97
106
  }
98
107
  case "reasoning":
99
108
  case "reasoning-delta": {
100
109
  const delta = pickString(chunk.textDelta) ?? pickString(chunk.delta) ?? pickString(chunk.text) ?? "";
101
- if (delta.length > 0) yield {
102
- type: "reasoning-delta",
103
- delta
110
+ if (delta.length > 0) {
111
+ if (!emittedStart) yield startEvent();
112
+ yield {
113
+ type: "reasoning-delta",
114
+ delta
115
+ };
116
+ }
117
+ break;
118
+ }
119
+ case "reasoning-signature": {
120
+ const signature = pickString(chunk.signature);
121
+ if (!emittedStart) yield startEvent();
122
+ yield {
123
+ type: "reasoning-end",
124
+ meta: {
125
+ provider: "anthropic",
126
+ ...signature !== void 0 ? { signature } : {}
127
+ }
128
+ };
129
+ break;
130
+ }
131
+ case "redacted-reasoning": {
132
+ const data = pickString(chunk.data);
133
+ if (!emittedStart) yield startEvent();
134
+ yield {
135
+ type: "reasoning-end",
136
+ meta: {
137
+ provider: "anthropic",
138
+ ...data !== void 0 ? { data } : {}
139
+ }
140
+ };
141
+ break;
142
+ }
143
+ case "reasoning-end": {
144
+ const anthropicMeta = typeof chunk.providerMetadata === "object" && chunk.providerMetadata !== null ? chunk.providerMetadata.anthropic : void 0;
145
+ const signature = pickString(anthropicMeta?.signature);
146
+ const data = pickString(anthropicMeta?.redactedData);
147
+ if (!emittedStart) yield startEvent();
148
+ yield {
149
+ type: "reasoning-end",
150
+ meta: {
151
+ provider: "anthropic",
152
+ ...signature !== void 0 ? { signature } : {},
153
+ ...data !== void 0 ? { data } : {}
154
+ }
104
155
  };
105
156
  break;
106
157
  }
@@ -108,31 +159,40 @@ async function* streamFromVercel(model, providerName, capabilities, req, overrid
108
159
  case "tool-input-start": {
109
160
  const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);
110
161
  const toolName = pickString(chunk.toolName);
111
- if (toolCallId !== void 0 && toolName !== void 0) yield {
112
- type: "tool-call-start",
113
- toolCallId,
114
- toolName
115
- };
162
+ if (toolCallId !== void 0 && toolName !== void 0) {
163
+ if (!emittedStart) yield startEvent();
164
+ yield {
165
+ type: "tool-call-start",
166
+ toolCallId,
167
+ toolName
168
+ };
169
+ }
116
170
  break;
117
171
  }
118
172
  case "tool-call-delta":
119
173
  case "tool-input-delta": {
120
174
  const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);
121
175
  const argsDelta = pickString(chunk.argsTextDelta) ?? pickString(chunk.delta) ?? pickString(chunk.inputTextDelta);
122
- if (toolCallId !== void 0 && argsDelta !== void 0 && argsDelta.length > 0) yield {
123
- type: "tool-call-input-delta",
124
- toolCallId,
125
- argsDelta
126
- };
176
+ if (toolCallId !== void 0 && argsDelta !== void 0 && argsDelta.length > 0) {
177
+ if (!emittedStart) yield startEvent();
178
+ yield {
179
+ type: "tool-call-input-delta",
180
+ toolCallId,
181
+ argsDelta
182
+ };
183
+ }
127
184
  break;
128
185
  }
129
186
  case "tool-call": {
130
187
  const toolCallId = pickString(chunk.toolCallId) ?? pickString(chunk.id);
131
- if (toolCallId !== void 0) yield {
132
- type: "tool-call-end",
133
- toolCallId,
134
- finalArgs: chunk.args ?? chunk.input
135
- };
188
+ if (toolCallId !== void 0) {
189
+ if (!emittedStart) yield startEvent();
190
+ yield {
191
+ type: "tool-call-end",
192
+ toolCallId,
193
+ finalArgs: chunk.args ?? chunk.input
194
+ };
195
+ }
136
196
  break;
137
197
  }
138
198
  case "finish":
@@ -141,11 +201,28 @@ async function* streamFromVercel(model, providerName, capabilities, req, overrid
141
201
  break;
142
202
  case "error": {
143
203
  const errorField = chunk.error;
204
+ const message = typeof errorField === "string" ? errorField : typeof errorField === "object" && errorField !== null ? pickString(errorField.message) ?? "unknown error" : "unknown error";
205
+ if (isAbortError(errorField) || req.signal?.aborted === true) {
206
+ finishReason = "aborted";
207
+ break;
208
+ }
209
+ const status = statusFromCause(errorField);
210
+ if (!emittedStart) {
211
+ const headers = headersFromCause(errorField);
212
+ throw new ProviderHttpError({
213
+ providerName,
214
+ status,
215
+ message,
216
+ cause: errorField,
217
+ ...headers !== void 0 ? { headers } : {}
218
+ });
219
+ }
220
+ sawError = true;
144
221
  yield {
145
222
  type: "error",
146
223
  error: {
147
- kind: "unknown",
148
- message: typeof errorField === "string" ? errorField : typeof errorField === "object" && errorField !== null ? pickString(errorField.message) ?? "unknown error" : "unknown error"
224
+ kind: classifyHttpStatus(status, message),
225
+ message
149
226
  }
150
227
  };
151
228
  break;
@@ -153,9 +230,10 @@ async function* streamFromVercel(model, providerName, capabilities, req, overrid
153
230
  default: break;
154
231
  }
155
232
  }
233
+ if (!emittedStart) yield startEvent();
156
234
  yield {
157
235
  type: "finish",
158
- finishReason,
236
+ finishReason: sawError ? "error" : finishReason,
159
237
  usage: finalUsage ?? {
160
238
  promptTokens: 0,
161
239
  completionTokens: 0,