@graphorin/provider 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +14 -9
- package/dist/adapters/llamacpp-server.d.ts +1 -1
- package/dist/adapters/llamacpp-server.js +1 -1
- package/dist/adapters/llamacpp-server.js.map +1 -1
- package/dist/adapters/ollama.d.ts.map +1 -1
- package/dist/adapters/ollama.js +3 -2
- package/dist/adapters/ollama.js.map +1 -1
- package/dist/adapters/openai-compatible.d.ts +14 -1
- package/dist/adapters/openai-compatible.d.ts.map +1 -1
- package/dist/adapters/openai-compatible.js +2 -0
- package/dist/adapters/openai-compatible.js.map +1 -1
- package/dist/adapters/vercel-messages.js +245 -0
- package/dist/adapters/vercel-messages.js.map +1 -0
- package/dist/adapters/vercel.d.ts +10 -8
- package/dist/adapters/vercel.d.ts.map +1 -1
- package/dist/adapters/vercel.js +54 -14
- package/dist/adapters/vercel.js.map +1 -1
- package/dist/counters/anthropic-wire.js +149 -0
- package/dist/counters/anthropic-wire.js.map +1 -0
- package/dist/counters/anthropic.d.ts +6 -1
- package/dist/counters/anthropic.d.ts.map +1 -1
- package/dist/counters/anthropic.js +25 -4
- package/dist/counters/anthropic.js.map +1 -1
- package/dist/counters/bedrock.js.map +1 -1
- package/dist/counters/dispatcher.d.ts.map +1 -1
- package/dist/counters/dispatcher.js +6 -4
- package/dist/counters/dispatcher.js.map +1 -1
- package/dist/counters/google.js.map +1 -1
- package/dist/counters/js-tiktoken.js.map +1 -1
- package/dist/errors/errors.d.ts +39 -11
- package/dist/errors/errors.d.ts.map +1 -1
- package/dist/errors/errors.js +54 -11
- package/dist/errors/errors.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/internal/abort.js +2 -2
- package/dist/internal/abort.js.map +1 -1
- package/dist/internal/http.js +19 -4
- package/dist/internal/http.js.map +1 -1
- package/dist/internal/openai-shaped.js +8 -3
- package/dist/internal/openai-shaped.js.map +1 -1
- package/dist/middleware/compose.d.ts +6 -6
- package/dist/middleware/compose.js +7 -7
- package/dist/middleware/compose.js.map +1 -1
- package/dist/middleware/production-hook.js.map +1 -1
- package/dist/middleware/with-cost-limit.d.ts +1 -1
- package/dist/middleware/with-cost-limit.js.map +1 -1
- package/dist/middleware/with-cost-tracking.d.ts +17 -4
- package/dist/middleware/with-cost-tracking.d.ts.map +1 -1
- package/dist/middleware/with-cost-tracking.js +12 -2
- package/dist/middleware/with-cost-tracking.js.map +1 -1
- package/dist/middleware/with-fallback.js +4 -2
- package/dist/middleware/with-fallback.js.map +1 -1
- package/dist/middleware/with-rate-limit.d.ts +2 -2
- package/dist/middleware/with-rate-limit.js.map +1 -1
- package/dist/middleware/with-redaction.d.ts +1 -1
- package/dist/middleware/with-redaction.js.map +1 -1
- package/dist/middleware/with-retry.js +3 -1
- package/dist/middleware/with-retry.js.map +1 -1
- package/dist/middleware/with-tracing.js +27 -6
- package/dist/middleware/with-tracing.js.map +1 -1
- package/dist/model-tier/classify.d.ts +1 -1
- package/dist/model-tier/classify.js +13 -5
- package/dist/model-tier/classify.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/provider.js.map +1 -1
- package/dist/reasoning/apply-policy.js.map +1 -1
- package/dist/reasoning/classify-contract.js +14 -5
- package/dist/reasoning/classify-contract.js.map +1 -1
- package/dist/reasoning/retention.js.map +1 -1
- package/dist/tool-examples.d.ts +1 -1
- package/dist/tool-examples.js +1 -1
- package/dist/tool-examples.js.map +1 -1
- package/dist/trust/classify-local-provider.d.ts +1 -1
- package/dist/trust/classify-local-provider.js.map +1 -1
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# @graphorin/provider
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`826ee59`](https://github.com/o-stepper/graphorin/commit/826ee5933ad38693b0dd2f20a110abfecba7d23d), [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
|
|
10
|
+
- @graphorin/observability@0.6.1
|
|
11
|
+
- @graphorin/core@0.6.1
|
|
12
|
+
|
|
13
|
+
## 0.6.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Prompt-cache economics end-to-end (audit 2026-07-04 Wave C, cluster C1).
|
|
18
|
+
|
|
19
|
+
- `Usage` gains `cachedReadTokens` / `cacheWriteTokens` (subsets of `promptTokens`), mapped by the vercel adapter (v7 `inputTokenDetails`; reasoning split kept exclusive of `completionTokens`) and the OpenAI-compatible adapter (`prompt_tokens_details.cached_tokens`); the fields flow through step/run aggregation, `usageByModel`, run-state (de)serialization and `withCostTracking` (new `cachedReadPerMtok`/`cacheWritePerMtok` lookup rates, full-input-rate fallback).
|
|
20
|
+
- New opt-in `ProviderRequest.cachePolicy` / `AgentConfig.cachePolicy` (`{ breakpoints: 'auto', ttl? }`): the vercel adapter anchors Anthropic `cache_control` on the first and last conversation messages so the stable prefix is written once and read at the discounted rate every later step.
|
|
21
|
+
- `ModelPrice` gains `cacheWriteUsdPerToken`; `calculateCost` bills cache writes; the bundled pricing snapshot is regenerated (2026-07-04) with current Anthropic 4.x / OpenAI gpt-5 + gpt-4.1 + o3/o4-mini / Gemini 2.5 families (legacy ids retained), `lookupPrice` resolves dated ids via a date-suffix fallback, and a new snapshot-coverage release gate cross-checks the model-tier classifier against the snapshot with an explicit known-unpriced allowlist (post-cutoff models report null cost + WARN instead of invented numbers).
|
|
22
|
+
- Cache-friendly catalogue: handoff tools serialize BEFORE the growing promoted section (byte-stable prefix under `tool_search` promotions), and new `toolPromotion: 'run-boundary'` freezes the advertised catalogue for a whole run while still persisting discoveries.
|
|
23
|
+
|
|
24
|
+
- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Tools/MCP alignment (audit 2026-07-04 Wave C, cluster C2).
|
|
25
|
+
|
|
26
|
+
- Worked examples now fold into the model-facing tool description inside the adapters themselves (vercel, openai-shaped, ollama; idempotent with the `createProvider` fold), so raw-adapter setups get them too.
|
|
27
|
+
- `searchDeferred` indexes name + description + tags + worked-example comments (BM25 and semantic legs), widening `tool_search` recall to the phrasing examples document.
|
|
28
|
+
- tools-06: `sandbox_violation` and `rate_limited` are now actually produced - a non-ok `SandboxResult` keeps its structured kind (violation/memory-exceeded -> `sandbox_violation`, sandbox timeout -> `timeout`), and tool authors can throw the new `ToolRateLimitError` (with `retryAfterMs`) to surface `rate_limited` with a pacing hint.
|
|
29
|
+
- SEP-1303 conformance suite: one test per producible `ToolErrorKind` pins that every failure returns as a model-visible outcome (never a protocol throw, batch never shrinks). Docs: MCP 2026-07-28 RC deprecation callout (sampling/roots/logging frozen; Tasks targets the extension shape).
|
|
30
|
+
|
|
31
|
+
- [#136](https://github.com/o-stepper/graphorin/pull/136) [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84) Thanks [@o-stepper](https://github.com/o-stepper)! - Trace-tree observability (audit 2026-07-04 Wave C, cluster C7; pairs periphery-04).
|
|
32
|
+
|
|
33
|
+
The agent loop now emits the previously-declared `agent.run` span per run and `agent.step` spans per step (parented under the run); `tool.execute` parents under the current step via the new optional `RunContext.span`; a `withTracing`-wrapped provider parents under the step via the new `ProviderRequest.parentSpan` (a live handle like `signal`). Attributes align to the OTel GenAI semantic conventions (`gen_ai.operation.name`, `gen_ai.agent.id/name`, `gen_ai.tool.name`, `gen_ai.request.model`, `gen_ai.usage.input_tokens/output_tokens`), parent-based sampling finally has parents to follow, and observability.md documents the real tree plus the memory-tier-spans-not-yet-parented limitation.
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- [#135](https://github.com/o-stepper/graphorin/pull/135) [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3) Thanks [@o-stepper](https://github.com/o-stepper)! - Provider adapters now match their real SDK / wire contracts (audit 2026-07-04 Wave B, cluster B1).
|
|
38
|
+
|
|
39
|
+
- vercel adapter: Graphorin tools convert to the AI SDK's name-keyed record with `jsonSchema()`-shaped input schemas; assistant `toolCalls` become `tool-call` content parts, `ToolMessage`s become `tool-result` messages, system-role messages hoist into the `system` option, and `toolChoice` maps onto the SDK spelling. Tool loops now run against the real `ai` peer (previously every tool conversation failed SDK validation). A real-SDK contract test suite (dev-only `ai` dependency) pins the shapes.
|
|
40
|
+
- Anthropic token counter posts Anthropic wire-shaped bodies (system hoist, `tool_use` / `tool_result` blocks, turn merging) instead of raw Graphorin messages that 400'd on any agent transcript; degradation to the tiktoken fallback now WARNs once.
|
|
41
|
+
- HTTP errors carry a canonical `errorKind` (shared `classifyHttpStatus` mapper: 429 rate-limit, 401/403 unauthorized, 5xx transient/capacity, context-length body sniff) plus captured `retry-after` / `x-ratelimit-*` headers; `withFallback` / `withRetry` consult them, so a 429 on the primary finally fails over and honours server-provided delays.
|
|
42
|
+
- llamacpp-node: the system prompt is no longer injected twice, per-request contexts/sequences are disposed after every stream (KV-cache leak), and aborted streams report `finishReason: 'aborted'`.
|
|
43
|
+
- OpenAI-compatible streaming sends `stream_options: { include_usage: true }` so vLLM / Together / OpenAI report real usage; `openAICompatibleAdapter` gained the `capabilities` / `timeoutMs` options its siblings had.
|
|
44
|
+
- `withCostTracking` bills separately-reported reasoning tokens at the output rate; classifiers recognise Bedrock cross-region ids (`us.anthropic.claude-...`) and the AI SDK's dotted provider ids.
|
|
45
|
+
|
|
46
|
+
- Updated dependencies [[`32f20c1`](https://github.com/o-stepper/graphorin/commit/32f20c110f184f8cef7eec85bf39f5f07c886cb6), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`4f850d9`](https://github.com/o-stepper/graphorin/commit/4f850d9bc0a05d6256c59c5117b010336fcb41d3), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`17a2d30`](https://github.com/o-stepper/graphorin/commit/17a2d30564154ca2ab87473335cdef43a5089c84), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`ce06b47`](https://github.com/o-stepper/graphorin/commit/ce06b472af9e30ac5d0792f7a8b6f42170a94627), [`6293a25`](https://github.com/o-stepper/graphorin/commit/6293a2531c5c9265294da22fb365a08f2bdf445a)]:
|
|
47
|
+
- @graphorin/core@0.6.0
|
|
48
|
+
- @graphorin/observability@0.6.0
|
|
49
|
+
|
|
50
|
+
## 0.5.0
|
|
51
|
+
|
|
52
|
+
First version published to the npm registry (with Sigstore build
|
|
53
|
+
provenance). The 0.2.0, 0.3.0, and 0.4.0 versions were internal lockstep
|
|
54
|
+
milestones and were never published. All `@graphorin/*` packages release
|
|
55
|
+
lockstep at the same version; the full release notes for 0.2.0-0.5.0 live
|
|
56
|
+
in the repository-level
|
|
57
|
+
[CHANGELOG](https://github.com/o-stepper/graphorin/blob/main/CHANGELOG.md).
|
|
58
|
+
|
|
3
59
|
## 0.1.0
|
|
4
60
|
|
|
5
61
|
Initial release. See the root `CHANGELOG.md` and the corresponding
|
package/README.md
CHANGED
|
@@ -5,19 +5,24 @@
|
|
|
5
5
|
|
|
6
6
|
The package owns four moving parts:
|
|
7
7
|
|
|
8
|
-
1. **`createProvider(...)`**
|
|
8
|
+
1. **`createProvider(...)`** - wraps any adapter in a stable `Provider`
|
|
9
9
|
shape with sensitivity, capability, and reasoning-retention defaults.
|
|
10
|
-
2. **Adapters**
|
|
10
|
+
2. **Adapters** - `vercelAdapter` (default cloud path; wraps the Vercel
|
|
11
11
|
AI SDK), `ollamaAdapter` (direct Ollama HTTP), `llamaCppServerAdapter`
|
|
12
12
|
(the upstream `llama-server` binary from llama.cpp), and
|
|
13
13
|
`openAICompatibleAdapter` (LMStudio / LocalAI / vLLM / Together-style
|
|
14
|
-
self-host endpoints).
|
|
15
|
-
|
|
14
|
+
self-host endpoints). Prompt caching: when a request carries
|
|
15
|
+
`cachePolicy: { breakpoints: 'auto' }`, the vercel adapter anchors
|
|
16
|
+
Anthropic `cache_control` breakpoints on the stable conversation
|
|
17
|
+
prefix, and cache read / write token legs flow back through
|
|
18
|
+
`Usage.cachedReadTokens` / `cacheWriteTokens`.
|
|
19
|
+
3. **Middleware** - `composeProviderMiddleware([...])` enforces a
|
|
16
20
|
canonical order at startup and throws `MiddlewareOrderingError` on
|
|
17
21
|
violation. Built-ins: `withTracing`, `withRetry`, `withRateLimit`,
|
|
18
|
-
`withCostLimit`, `withCostTracking
|
|
22
|
+
`withCostLimit`, `withCostTracking` (cache-aware: bills cache reads
|
|
23
|
+
and writes at their own rates), `withFallback`, and
|
|
19
24
|
`withRedaction` (mandatory in production).
|
|
20
|
-
4. **Token counting**
|
|
25
|
+
4. **Token counting** - pluggable `TokenCounter` dispatcher. Default
|
|
21
26
|
`JsTiktokenCounter` for OpenAI-compatible models; per-vendor native
|
|
22
27
|
counters for Anthropic, Google, and Bedrock; heuristic fallback for
|
|
23
28
|
unknown providers with a one-time WARN.
|
|
@@ -72,7 +77,7 @@ const safeProvider = composeProviderMiddleware([
|
|
|
72
77
|
```ts
|
|
73
78
|
import { ollamaAdapter } from '@graphorin/provider/adapters/ollama';
|
|
74
79
|
|
|
75
|
-
// Auto-classified as 'loopback'
|
|
80
|
+
// Auto-classified as 'loopback' - no warning, no first-run prompt.
|
|
76
81
|
const local = createProvider(
|
|
77
82
|
ollamaAdapter({ model: 'llama3.1:8b', baseUrl: 'http://127.0.0.1:11434' }),
|
|
78
83
|
);
|
|
@@ -81,7 +86,7 @@ const local = createProvider(
|
|
|
81
86
|
The same `LocalProviderTrust` classifier (`'loopback' | 'private' |
|
|
82
87
|
'public-tls' | 'public-cleartext'`) drives the trust auto-detection,
|
|
83
88
|
the sensitivity-tier defaults, and the `withRedaction` policy table
|
|
84
|
-
for every `baseUrl`-driven adapter
|
|
89
|
+
for every `baseUrl`-driven adapter - `ollamaAdapter`,
|
|
85
90
|
`llamaCppServerAdapter`, and `openAICompatibleAdapter`. The classifier
|
|
86
91
|
lives at `@graphorin/provider/trust`. Public-cleartext URLs refuse to
|
|
87
92
|
start with `LocalProviderInsecureTransportError`.
|
|
@@ -101,5 +106,5 @@ recommendations.
|
|
|
101
106
|
|
|
102
107
|
## Project metadata
|
|
103
108
|
|
|
104
|
-
- **Project Graphorin** · v0.
|
|
109
|
+
- **Project Graphorin** · v0.6.1 · MIT License · © 2026 Oleksiy Stepurenko
|
|
105
110
|
- Repository: <https://github.com/o-stepper/graphorin>
|
|
@@ -47,7 +47,7 @@ interface LlamaCppServerAdapterOptions {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* Build a Graphorin {@link Provider} backed by the upstream
|
|
50
|
-
* `llama-server` binary. The factory does not start the binary
|
|
50
|
+
* `llama-server` binary. The factory does not start the binary -
|
|
51
51
|
* operators launch it themselves with the desired model + GPU flags
|
|
52
52
|
* and pass the URL here.
|
|
53
53
|
*
|
|
@@ -9,7 +9,7 @@ import { buildOpenAIShapedProvider } from "../internal/openai-shaped.js";
|
|
|
9
9
|
const DEFAULT_LLAMACPP_SERVER_BASE_URL = "http://127.0.0.1:8080";
|
|
10
10
|
/**
|
|
11
11
|
* Build a Graphorin {@link Provider} backed by the upstream
|
|
12
|
-
* `llama-server` binary. The factory does not start the binary
|
|
12
|
+
* `llama-server` binary. The factory does not start the binary -
|
|
13
13
|
* operators launch it themselves with the desired model + GPU flags
|
|
14
14
|
* and pass the URL here.
|
|
15
15
|
*
|
|
@@ -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`
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ollama.d.ts","names":[],"sources":["../../src/adapters/ollama.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
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"}
|
package/dist/adapters/ollama.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LocalProviderInsecureTransportError, ProviderStreamParseError } from "../errors/errors.js";
|
|
2
2
|
import { applyReasoningPolicy } from "../reasoning/apply-policy.js";
|
|
3
3
|
import { resolveReasoningRetention } from "../reasoning/retention.js";
|
|
4
|
+
import { foldToolExamples } from "../tool-examples.js";
|
|
4
5
|
import { classifyLocalProvider } from "../trust/classify-local-provider.js";
|
|
5
6
|
import { callJsonHttp, makeStreamStartEvent, toOllamaChatMessages } from "../internal/http.js";
|
|
6
7
|
import { parseNdJsonStream } from "../internal/sse.js";
|
|
@@ -13,7 +14,7 @@ import { randomUUID } from "node:crypto";
|
|
|
13
14
|
* native Ollama streaming JSON protocol (`POST /api/chat` returning
|
|
14
15
|
* newline-delimited JSON objects). For operators who prefer the
|
|
15
16
|
* OpenAI-compatible variant exposed by recent Ollama releases, the
|
|
16
|
-
* generic {@link openAICompatibleAdapter} is the better choice
|
|
17
|
+
* generic {@link openAICompatibleAdapter} is the better choice - both
|
|
17
18
|
* adapters share the same {@link LocalProviderTrust} classifier and
|
|
18
19
|
* {@link LocalProviderInsecureTransportError} startup behaviour.
|
|
19
20
|
*
|
|
@@ -191,7 +192,7 @@ function buildBody(model, req, stream, structuredOutput) {
|
|
|
191
192
|
if (req.maxTokens !== void 0) optionsBlock.num_predict = req.maxTokens;
|
|
192
193
|
body.options = optionsBlock;
|
|
193
194
|
}
|
|
194
|
-
if (req.tools !== void 0 && req.tools.length > 0) body.tools = req.tools.map((t) => ({
|
|
195
|
+
if (req.tools !== void 0 && req.tools.length > 0) body.tools = foldToolExamples(req.tools).map((t) => ({
|
|
195
196
|
type: "function",
|
|
196
197
|
function: {
|
|
197
198
|
name: t.name,
|
|
@@ -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 {\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 body.tools = 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 {@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,4 +1,4 @@
|
|
|
1
|
-
import { Provider, Sensitivity } from "@graphorin/core";
|
|
1
|
+
import { Provider, ProviderCapabilities, Sensitivity } from "@graphorin/core";
|
|
2
2
|
|
|
3
3
|
//#region src/adapters/openai-compatible.d.ts
|
|
4
4
|
|
|
@@ -28,6 +28,19 @@ interface OpenAICompatibleAdapterOptions {
|
|
|
28
28
|
* public host.
|
|
29
29
|
*/
|
|
30
30
|
readonly allowInsecureTransport?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Capability overrides merged on top of the adapter defaults
|
|
33
|
+
* (core-provider-10). Use them to widen `contextWindow` /
|
|
34
|
+
* `maxOutput` for large-context servers or to set
|
|
35
|
+
* `structuredOutput: false` for servers that reject
|
|
36
|
+
* `response_format`.
|
|
37
|
+
*/
|
|
38
|
+
readonly capabilities?: Partial<ProviderCapabilities>;
|
|
39
|
+
/**
|
|
40
|
+
* Time-to-response budget per request (PS-24). Default 120s; `0`
|
|
41
|
+
* disables.
|
|
42
|
+
*/
|
|
43
|
+
readonly timeoutMs?: number;
|
|
31
44
|
/** Override for the default `acceptsSensitivity` value. */
|
|
32
45
|
readonly acceptsSensitivity?: ReadonlyArray<Sensitivity>;
|
|
33
46
|
/** Provider name attached to spans / log lines. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-compatible.d.ts","names":[],"sources":["../../src/adapters/openai-compatible.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAwBiB,8BAAA;;;;;;;;;;;;;qBAaI,SAAS;;8BAEA;;;;;;;
|
|
1
|
+
{"version":3,"file":"openai-compatible.d.ts","names":[],"sources":["../../src/adapters/openai-compatible.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAwBiB,8BAAA;;;;;;;;;;;;;qBAaI,SAAS;;8BAEA;;;;;;;;;;;;;0BAaJ,QAAQ;;;;;;;gCAOF,cAAc;;;;;;;;;;;;;iBAc9B,uBAAA,UAAiC,iCAAiC"}
|
|
@@ -18,6 +18,8 @@ function openAICompatibleAdapter(options) {
|
|
|
18
18
|
...options.headers !== void 0 ? { headers: options.headers } : {},
|
|
19
19
|
...options.fetchImpl !== void 0 ? { fetchImpl: options.fetchImpl } : {},
|
|
20
20
|
...options.allowInsecureTransport !== void 0 ? { allowInsecureTransport: options.allowInsecureTransport } : {},
|
|
21
|
+
...options.capabilities !== void 0 ? { capabilities: options.capabilities } : {},
|
|
22
|
+
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
|
|
21
23
|
...options.acceptsSensitivity !== void 0 ? { acceptsSensitivity: options.acceptsSensitivity } : {},
|
|
22
24
|
...options.logger !== void 0 ? { logger: options.logger } : {}
|
|
23
25
|
}).provider;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai-compatible.js","names":[],"sources":["../../src/adapters/openai-compatible.ts"],"sourcesContent":["/**\n * Generic OpenAI-compatible adapter
|
|
1
|
+
{"version":3,"file":"openai-compatible.js","names":[],"sources":["../../src/adapters/openai-compatible.ts"],"sourcesContent":["/**\n * Generic OpenAI-compatible adapter - works against any HTTP server\n * that speaks the `/v1/chat/completions` REST contract. Tested\n * deployments include LMStudio (default port 1234), LocalAI (default\n * port 8080), vLLM (`python -m vllm.entrypoints.openai.api_server`,\n * default port 8000), Together-style self-host endpoints, and any\n * other server in the OpenAI-compatible ecosystem.\n *\n * The adapter shares the same `LocalProviderTrust` classifier as\n * `ollamaAdapter` and `llamaCppServerAdapter` - one classifier, one\n * policy table, one error type.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, ProviderCapabilities, Sensitivity } from '@graphorin/core';\n\nimport { buildOpenAIShapedProvider } from '../internal/openai-shaped.js';\n\n/**\n * Options accepted by {@link openAICompatibleAdapter}.\n *\n * @stable\n */\nexport interface OpenAICompatibleAdapterOptions {\n /** Model identifier sent in the request body's `model` field. */\n readonly model: string;\n /**\n * Base URL of the OpenAI-compatible server. The classifier inspects\n * the protocol + host to assign a `LocalProviderTrust` value.\n */\n readonly baseUrl: string;\n /** Optional REST path override. Defaults to `/v1/chat/completions`. */\n readonly chatPath?: string;\n /** Optional bearer-auth API key. */\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 * Acknowledge the risk of running over plaintext HTTP against a\n * public host.\n */\n readonly allowInsecureTransport?: boolean;\n /**\n * Capability overrides merged on top of the adapter defaults\n * (core-provider-10). Use them to widen `contextWindow` /\n * `maxOutput` for large-context servers or to set\n * `structuredOutput: false` for servers that reject\n * `response_format`.\n */\n readonly capabilities?: Partial<ProviderCapabilities>;\n /**\n * Time-to-response budget per request (PS-24). Default 120s; `0`\n * disables.\n */\n readonly timeoutMs?: number;\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. */\n readonly logger?: (level: 'warn' | 'info', message: string, meta?: object) => void;\n}\n\n/**\n * Build a Graphorin {@link Provider} backed by an OpenAI-compatible\n * HTTP server. The same code path serves LMStudio, LocalAI, vLLM, and\n * any other compatible self-host endpoint.\n *\n * @stable\n */\nexport function openAICompatibleAdapter(options: OpenAICompatibleAdapterOptions): Provider {\n const providerName = options.name ?? `openai-compatible-${options.model}`;\n const built = buildOpenAIShapedProvider({\n providerName,\n model: options.model,\n baseUrl: options.baseUrl,\n ...(options.chatPath !== undefined ? { chatPath: options.chatPath } : {}),\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.capabilities !== undefined ? { capabilities: options.capabilities } : {}),\n ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),\n ...(options.acceptsSensitivity !== undefined\n ? { acceptsSensitivity: options.acceptsSensitivity }\n : {}),\n ...(options.logger !== undefined ? { logger: options.logger } : {}),\n });\n return built.provider;\n}\n"],"mappings":";;;;;;;;;;AAyEA,SAAgB,wBAAwB,SAAmD;AAoBzF,QAlBc,0BAA0B;EACtC,cAFmB,QAAQ,QAAQ,qBAAqB,QAAQ;EAGhE,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,GAAI,QAAQ,aAAa,SAAY,EAAE,UAAU,QAAQ,UAAU,GAAG,EAAE;EACxE,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,iBAAiB,SAAY,EAAE,cAAc,QAAQ,cAAc,GAAG,EAAE;EACpF,GAAI,QAAQ,cAAc,SAAY,EAAE,WAAW,QAAQ,WAAW,GAAG,EAAE;EAC3E,GAAI,QAAQ,uBAAuB,SAC/B,EAAE,oBAAoB,QAAQ,oBAAoB,GAClD,EAAE;EACN,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE;EACnE,CAAC,CACW"}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
//#region src/adapters/vercel-messages.ts
|
|
2
|
+
/**
|
|
3
|
+
* Registered symbols used by the AI SDK's `asSchema()` detection. They
|
|
4
|
+
* are created with `Symbol.for(...)` in the SDK, so a structurally
|
|
5
|
+
* hand-rolled wrapper is indistinguishable from the SDK's own
|
|
6
|
+
* `jsonSchema()` output - including across duplicated copies of the
|
|
7
|
+
* SDK in a node_modules tree.
|
|
8
|
+
*/
|
|
9
|
+
const AI_SDK_SCHEMA_SYMBOL = Symbol.for("vercel.ai.schema");
|
|
10
|
+
const AI_SDK_VALIDATOR_SYMBOL = Symbol.for("vercel.ai.validator");
|
|
11
|
+
/**
|
|
12
|
+
* Wrap a plain JSON Schema record into the AI SDK `Schema` shape the
|
|
13
|
+
* SDK's `asSchema()` accepts. Equivalent to importing `jsonSchema`
|
|
14
|
+
* from `ai`, but hand-rolled so the converter works with fixture
|
|
15
|
+
* runtimes and does not force the optional peer at module scope.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
function wrapAsAiSdkSchema(schema) {
|
|
20
|
+
return {
|
|
21
|
+
[AI_SDK_SCHEMA_SYMBOL]: true,
|
|
22
|
+
[AI_SDK_VALIDATOR_SYMBOL]: true,
|
|
23
|
+
_type: void 0,
|
|
24
|
+
jsonSchema: schema,
|
|
25
|
+
validate: void 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert Graphorin `ToolDefinition[]` to the AI SDK's name-keyed tool
|
|
30
|
+
* record.
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
function toAiSdkTools(tools) {
|
|
35
|
+
const record = {};
|
|
36
|
+
for (const t of tools) {
|
|
37
|
+
const schema = wrapAsAiSdkSchema(t.inputSchema);
|
|
38
|
+
record[t.name] = {
|
|
39
|
+
...t.description !== void 0 ? { description: t.description } : {},
|
|
40
|
+
inputSchema: schema,
|
|
41
|
+
parameters: schema
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return record;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Convert a Graphorin `ToolChoice` to the AI SDK's spelling. The
|
|
48
|
+
* string modes are identical; the named-tool form differs
|
|
49
|
+
* (`{tool: name}` vs `{type: 'tool', toolName: name}`).
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
function toAiSdkToolChoice(choice) {
|
|
54
|
+
if (choice === "auto" || choice === "required" || choice === "none") return choice;
|
|
55
|
+
return {
|
|
56
|
+
type: "tool",
|
|
57
|
+
toolName: choice.tool
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Convert Graphorin messages to AI SDK `ModelMessage`s:
|
|
62
|
+
*
|
|
63
|
+
* - system-role messages are hoisted into `system` (v7 rejects them
|
|
64
|
+
* inside the `messages` array outright),
|
|
65
|
+
* - assistant `toolCalls` become `tool-call` content parts,
|
|
66
|
+
* - `ToolMessage`s become `{role: 'tool'}` messages carrying a
|
|
67
|
+
* `tool-result` part whose `toolName` is recovered from the paired
|
|
68
|
+
* assistant call earlier in the transcript,
|
|
69
|
+
* - binary content parts are renamed onto the SDK's part vocabulary
|
|
70
|
+
* (`file` parts carry `data`; audio has no dedicated user part and
|
|
71
|
+
* rides as a `file` part).
|
|
72
|
+
*
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
function toAiSdkPrompt(messages) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const systemParts = [];
|
|
78
|
+
const toolNameById = /* @__PURE__ */ new Map();
|
|
79
|
+
for (const msg of messages) switch (msg.role) {
|
|
80
|
+
case "system":
|
|
81
|
+
if (msg.content.length > 0) systemParts.push(msg.content);
|
|
82
|
+
break;
|
|
83
|
+
case "user":
|
|
84
|
+
out.push({
|
|
85
|
+
role: "user",
|
|
86
|
+
content: typeof msg.content === "string" ? msg.content : msg.content.flatMap(toUserPart)
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
case "assistant": {
|
|
90
|
+
for (const call of msg.toolCalls ?? []) toolNameById.set(call.toolCallId, call.toolName);
|
|
91
|
+
if (!(msg.toolCalls !== void 0 && msg.toolCalls.length > 0) && typeof msg.content === "string") {
|
|
92
|
+
out.push({
|
|
93
|
+
role: "assistant",
|
|
94
|
+
content: msg.content
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
const parts = [];
|
|
99
|
+
if (typeof msg.content === "string") {
|
|
100
|
+
if (msg.content.length > 0) parts.push({
|
|
101
|
+
type: "text",
|
|
102
|
+
text: msg.content
|
|
103
|
+
});
|
|
104
|
+
} else for (const part of msg.content) parts.push(...toAssistantPart(part));
|
|
105
|
+
for (const call of msg.toolCalls ?? []) parts.push({
|
|
106
|
+
type: "tool-call",
|
|
107
|
+
toolCallId: call.toolCallId,
|
|
108
|
+
toolName: call.toolName,
|
|
109
|
+
input: call.args,
|
|
110
|
+
args: call.args
|
|
111
|
+
});
|
|
112
|
+
out.push({
|
|
113
|
+
role: "assistant",
|
|
114
|
+
content: parts
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "tool": {
|
|
119
|
+
const text = flattenToText(msg.content);
|
|
120
|
+
out.push({
|
|
121
|
+
role: "tool",
|
|
122
|
+
content: [{
|
|
123
|
+
type: "tool-result",
|
|
124
|
+
toolCallId: msg.toolCallId,
|
|
125
|
+
toolName: toolNameById.get(msg.toolCallId) ?? "unknown_tool",
|
|
126
|
+
output: {
|
|
127
|
+
type: "text",
|
|
128
|
+
value: text
|
|
129
|
+
},
|
|
130
|
+
result: text
|
|
131
|
+
}]
|
|
132
|
+
});
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
messages: out,
|
|
138
|
+
...systemParts.length > 0 ? { system: systemParts.join("\n\n") } : {}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function toUserPart(part) {
|
|
142
|
+
switch (part.type) {
|
|
143
|
+
case "text": return [{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: part.text
|
|
146
|
+
}];
|
|
147
|
+
case "image": return [{
|
|
148
|
+
type: "image",
|
|
149
|
+
image: part.image,
|
|
150
|
+
...part.mimeType !== void 0 ? {
|
|
151
|
+
mediaType: part.mimeType,
|
|
152
|
+
mimeType: part.mimeType
|
|
153
|
+
} : {}
|
|
154
|
+
}];
|
|
155
|
+
case "audio": return [filePart(part.audio, part.mimeType ?? "application/octet-stream")];
|
|
156
|
+
case "file": return [filePart(part.file, part.mimeType, part.filename)];
|
|
157
|
+
case "reasoning": return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function toAssistantPart(part) {
|
|
161
|
+
switch (part.type) {
|
|
162
|
+
case "text": return part.text.length > 0 ? [{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: part.text
|
|
165
|
+
}] : [];
|
|
166
|
+
case "reasoning": {
|
|
167
|
+
const meta = part.meta;
|
|
168
|
+
const anthropic = {};
|
|
169
|
+
if (meta?.provider === "anthropic") {
|
|
170
|
+
if (typeof meta.signature === "string") anthropic.signature = meta.signature;
|
|
171
|
+
if (typeof meta.data === "string") anthropic.redactedData = meta.data;
|
|
172
|
+
}
|
|
173
|
+
return [{
|
|
174
|
+
type: "reasoning",
|
|
175
|
+
text: part.text,
|
|
176
|
+
...Object.keys(anthropic).length > 0 ? { providerOptions: { anthropic } } : {}
|
|
177
|
+
}];
|
|
178
|
+
}
|
|
179
|
+
case "image": return [filePart(part.image, part.mimeType ?? "image/png")];
|
|
180
|
+
case "audio": return [filePart(part.audio, part.mimeType ?? "application/octet-stream")];
|
|
181
|
+
case "file": return [filePart(part.file, part.mimeType, part.filename)];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function filePart(data, mimeType, filename) {
|
|
185
|
+
return {
|
|
186
|
+
type: "file",
|
|
187
|
+
data,
|
|
188
|
+
mediaType: mimeType,
|
|
189
|
+
mimeType,
|
|
190
|
+
...filename !== void 0 ? { filename } : {}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function flattenToText(content) {
|
|
194
|
+
if (typeof content === "string") return content;
|
|
195
|
+
const buffer = [];
|
|
196
|
+
for (const part of content) if (part.type === "text" || part.type === "reasoning") buffer.push(part.text);
|
|
197
|
+
return buffer.join("");
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Decorate the first and last converted conversation messages with an
|
|
201
|
+
* Anthropic `cacheControl` provider option (core-provider-02).
|
|
202
|
+
*
|
|
203
|
+
* The first-message anchor makes tools + system + the stable prefix a
|
|
204
|
+
* cache segment that is written once and read on every later step; the
|
|
205
|
+
* last-message anchor caches the whole conversation so each step's write
|
|
206
|
+
* becomes the next step's read. Non-Anthropic models ignore the option.
|
|
207
|
+
* A single-message conversation gets one anchor.
|
|
208
|
+
*/
|
|
209
|
+
function applyCacheAnchors(messages, ttl) {
|
|
210
|
+
if (messages.length === 0) return messages;
|
|
211
|
+
const cacheControl = {
|
|
212
|
+
type: "ephemeral",
|
|
213
|
+
...ttl === "1h" ? { ttl: "1h" } : {}
|
|
214
|
+
};
|
|
215
|
+
const decorate = (msg) => {
|
|
216
|
+
const existing = typeof msg.providerOptions === "object" && msg.providerOptions !== null ? msg.providerOptions : {};
|
|
217
|
+
const anthropic = typeof existing.anthropic === "object" && existing.anthropic !== null ? existing.anthropic : {};
|
|
218
|
+
return {
|
|
219
|
+
...msg,
|
|
220
|
+
providerOptions: {
|
|
221
|
+
...existing,
|
|
222
|
+
anthropic: {
|
|
223
|
+
...anthropic,
|
|
224
|
+
cacheControl
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
if (messages.length === 1) {
|
|
230
|
+
const only = messages[0];
|
|
231
|
+
return only === void 0 ? messages : [decorate(only)];
|
|
232
|
+
}
|
|
233
|
+
const first = messages[0];
|
|
234
|
+
const last = messages[messages.length - 1];
|
|
235
|
+
if (first === void 0 || last === void 0) return messages;
|
|
236
|
+
return [
|
|
237
|
+
decorate(first),
|
|
238
|
+
...messages.slice(1, -1),
|
|
239
|
+
decorate(last)
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
export { applyCacheAnchors, toAiSdkPrompt, toAiSdkToolChoice, toAiSdkTools };
|
|
245
|
+
//# sourceMappingURL=vercel-messages.js.map
|