octaflow 0.34.0 → 0.35.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.
package/README.md CHANGED
@@ -218,7 +218,7 @@ next to what your steps actually do.
218
218
  | 🔭 | **Observability** — lifecycle events (run history), per-step spans, and `createMeterObserver` turning the events into OpenTelemetry-style metrics — all pluggable, all structural |
219
219
  | 🤖 | **AI add-on** — instrumented models, token/cost capture, quota, daily rollups, a `usage` sink that books each step into `@octabits-io/metering` (the paid APIs around the model in the same table, priced at read time), refusal-aware models, lenient JSON from local models (`withJsonRepair`), a vendor's refusal explained by key and hint (`explainProviderError`), a per-run circuit breaker, a connection test (`testProvider`), Ollama's native API as language and embedding models (`createOllama` in `octaflow/ai/providers`: `num_ctx`, thinking and `keep_alive` per model or per call, no dependency) |
220
220
  | 🗂️ | **Model providers an administrator manages** — `createModelProviders` (`octaflow/ai`): providers as rows, each with its kind, server, region, a key that crosses a cipher seam, and its models with declared capabilities (temperature, JSON mode, vision, video, tools, embedding); `createPgModelProviderStore` + `modelProviderDdl` (`octaflow/postgres`), `modelProviderColumns` (`octaflow/drizzle`); `createModelResolver` (`octaflow/ai/providers`) builds the AI SDK model from a row, shapes each call to what the model declares, hands the host its `destination` for the disclosure guard, and rebuilds after a write |
221
- | 🏃 | **Model runners** — a model the app cannot reach, served by a process that connects *out*: a provider row of kind `runner` carries tags instead of a URL, a runner registered with a token (`octaflow/ai/runner`: `createModelRunnerHub`, shown once, kept as a hash) subscribes with its tags, claims the calls by long-poll through `@octabits-io/server/fetch/octaflow`'s `createModelRunnerHandler` and posts the answers back; the mailbox is two tables (`createPgModelRunnerStore` + `modelRunnerDdl`, `modelRunnerColumns` + `modelRunnerRequestColumns`) whose rows live only while a call is in flight; nobody online fails the call at once, so the breaker trips and the probe answers in milliseconds |
221
+ | 🏃 | **Model runners** — a model the app cannot reach, served by a process that connects *out*: a provider row of kind `runner` carries tags instead of a URL, a runner registered with a token (`octaflow/ai/runner`: `createModelRunnerHub`, shown once, kept as a hash) subscribes with its tags, claims the calls by long-poll through `@octabits-io/server/fetch/octaflow`'s `createModelRunnerHandler` and posts the answers back; the mailbox is three tables (`createPgModelRunnerStore` + `modelRunnerDdl`; `modelRunnerColumns`, `modelRunnerRequestColumns`, `modelRunnerChunkColumns`) whose rows live only while a call is in flight, a streamed answer's parts chunk by chunk among them; nobody online fails the call at once, so the breaker trips and the probe answers in milliseconds; `octaflow-runner` is the runner as a process (Ollama or any OpenAI-compatible server) |
222
222
  | 🧱 | **Pluggable everything** — `WorkflowStore`, `Dispatcher`, `StepGate`, `FlowObserver`, hooks |
223
223
 
224
224
  ---
@@ -251,10 +251,10 @@ pnpm add ai @ai-sdk/provider
251
251
 
252
252
  ### The Postgres tables
253
253
 
254
- `octaflow/postgres` owns eight tables and ships their DDL: `flowStoreDdl()` (`flow_workflow`,
254
+ `octaflow/postgres` owns nine tables and ships their DDL: `flowStoreDdl()` (`flow_workflow`,
255
255
  `flow_workflow_step`), `flowEventDdl()` (`flow_step_event`), `flowGateDdl()`
256
256
  (`flow_rate_bucket`, `flow_step_lease`), `modelProviderDdl()` (`model_provider`) and
257
- `modelRunnerDdl()` (`model_runner`, `model_runner_request`). Each is idempotent `CREATE TABLE IF NOT EXISTS`, takes
257
+ `modelRunnerDdl()` (`model_runner`, `model_runner_request`, `model_runner_chunk`). Each is idempotent `CREATE TABLE IF NOT EXISTS`, takes
258
258
  a `schema`, and is meant to be pasted into your own migration system; `applySchema(pool, ddl)`
259
259
  is the dev and test shortcut.
260
260
 
@@ -273,8 +273,8 @@ migration. Where the store's queries are one SQL statement by nature — every o
273
273
  is — `./drizzle` ships the columns only, and the store stays the SQL one. So
274
274
  `octaflow/drizzle` exports `flowWorkflowColumns`, `flowWorkflowStepColumns`,
275
275
  `flowStepEventColumns`, `flowRateBucketColumns`, `flowStepLeaseColumns`, `modelProviderColumns`,
276
- `modelRunnerColumns` and `modelRunnerRequestColumns` (`drizzle-orm` an optional peer) and
277
- nothing that runs a query. The foreign keys, the composite keys, the unique
276
+ `modelRunnerColumns`, `modelRunnerRequestColumns` and `modelRunnerChunkColumns` (`drizzle-orm` an
277
+ optional peer) and nothing that runs a query. The foreign keys, the composite keys, the unique
278
278
  constraint and the indexes are yours to declare on the `pgTable`; each column set's doc lists
279
279
  the ones its DDL creates, and two of them the store relies on:
280
280
 
@@ -343,7 +343,8 @@ const { token } = (await runners.registerRunner({ id: 'dani-mac', label: "Dani's
343
343
  import { createModelRunnerHandler } from '@octabits-io/server/fetch/octaflow';
344
344
  export const POST = compose(withErrorMapping())(createModelRunnerHandler({ prefix: '/api/model-runner', hub: runners }));
345
345
 
346
- // the runner, on the machine with the model
346
+ // the runner, on the machine with the model — the bin, or the loop in your own process
347
+ // npx octaflow-runner --url https://app.example/api/model-runner --token omr_… --ollama http://localhost:11434
347
348
  import { createHttpRunnerClient, runModelRunner } from 'octaflow/ai/runner';
348
349
  import { createOllama } from 'octaflow/ai/providers';
349
350
  await runModelRunner({
@@ -352,6 +353,15 @@ await runModelRunner({
352
353
  });
353
354
  ```
354
355
 
356
+ **`octaflow-runner`**, the bin, is the runner as a process: `--url` and `--token` (or
357
+ `OCTAFLOW_RUNNER_URL` / `OCTAFLOW_RUNNER_TOKEN`), one backend — `--ollama <url>` for Ollama's
358
+ native API, `--openai-compatible <url>` (with `--api-key`) for mlx_lm.server, vLLM or llama.cpp
359
+ through the optional `@ai-sdk/openai-compatible` peer — and `--concurrency`. At start it says
360
+ who the token belongs to, which models the app's provider rows declare for its tags (the door
361
+ tells it) and which of them the local server has, with the `ollama pull` for a missing one;
362
+ `--check` does that and exits. A door it cannot reach is retried with a backoff, forever.
363
+ The skill `serve-a-model-from-a-runner` walks through the four pieces.
364
+
355
365
  A runner may claim a request when it has **every tag** of the request — the provider row's
356
366
  tags — as a CI job's labels must all be on the runner. A call with nobody online for its
357
367
  tags fails at once with `provider_unreachable`, so `withCircuitBreaker` trips and
@@ -363,10 +373,13 @@ every provider, and the disclosure gate reads it the same way.
363
373
 
364
374
  The wire is the AI SDK's own call and answer as JSON — binary file data as base64, a URL
365
375
  restored on the other side — so the runner's backend is any `LanguageModelV4`: `createOllama`
366
- from this package, or `@ai-sdk/openai-compatible` for mlx_lm.server, vLLM and llama.cpp. The
367
- first version answers whole: `streamText` over a runner model works, emitted as one text
368
- block when the answer is there. Files reach the runner as bytes, since the runner model
369
- declares no URL support and the SDK downloads on the app's side. Embeddings go the same way.
376
+ from this package, or `@ai-sdk/openai-compatible` for mlx_lm.server, vLLM and llama.cpp.
377
+ `streamText` streams: the runner runs `doStream` and posts the parts in chunks as they come
378
+ (every 100 ms, or 64 parts), a third table carries them, and the app emits each chunk as it
379
+ lands; a chunk the door refuses tells the runner the caller gave up, and it cancels the
380
+ stream. A runner on the older wire answers a stream whole, and the app emits that as parts
381
+ too. Files reach the runner as bytes, since the runner model declares no URL support and the
382
+ SDK downloads on the app's side. Embeddings go the same way.
370
383
 
371
384
  ---
372
385
 
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * octaflow-runner — serve a model to an app from a machine the app cannot reach.
4
+ *
5
+ * The runner connects *out* to the app's door (`@octabits-io/server/fetch/octaflow`'s
6
+ * `createModelRunnerHandler`) with the token an administrator minted for it, asks for the model
7
+ * calls that carry its tags and runs them on the server beside it — Ollama, or anything with
8
+ * OpenAI's chat API (mlx_lm.server, vLLM, llama.cpp) — and posts the answers back. No port is
9
+ * opened; the machine needs a way out and nothing else.
10
+ *
11
+ * octaflow-runner --url https://app.example/api/model-runner --token omr_… --ollama http://localhost:11434
12
+ * octaflow-runner --url … --token … --openai-compatible http://localhost:8081/v1 [--api-key …]
13
+ *
14
+ * --url <url> the door; or OCTAFLOW_RUNNER_URL
15
+ * --token <token> the runner's token, shown once when it was registered; or OCTAFLOW_RUNNER_TOKEN
16
+ * --ollama <url> serve from Ollama's native API at this URL (num_ctx, thinking, keep_alive per model honoured)
17
+ * --openai-compatible <url> serve from an OpenAI-compatible server at this base URL (its `/v1`)
18
+ * --api-key <key> the key that server wants, if any; or OCTAFLOW_RUNNER_API_KEY
19
+ * --concurrency <n> requests in flight at once (default 1 — mlx_lm.server answers one at a time); the door caps it at what was registered
20
+ * --check connect, say who the token belongs to and whether each model the app declares is installed, then exit
21
+ * --quiet log failures only
22
+ * --help
23
+ *
24
+ * At start the runner says who it is and which models the app's provider rows declare for its
25
+ * tags, and asks the local server which of them it has: a missing one is a warning with the
26
+ * command that installs it, not a refusal — the app may declare a model it will ask for later.
27
+ */
28
+ import { parseArgs } from 'node:util';
29
+ import { fileURLToPath } from 'node:url';
30
+
31
+ const USAGE = `octaflow-runner — serve a model to an app from a machine the app cannot reach
32
+
33
+ octaflow-runner --url <door> --token <token> (--ollama <url> | --openai-compatible <url> [--api-key <key>]) [--concurrency <n>] [--check] [--quiet]
34
+
35
+ --url the app's door (OCTAFLOW_RUNNER_URL)
36
+ --token the runner's token (OCTAFLOW_RUNNER_TOKEN)
37
+ --ollama Ollama's native API, e.g. http://localhost:11434
38
+ --openai-compatible an OpenAI-compatible base URL, e.g. http://localhost:8081/v1 (mlx_lm.server, vLLM, llama.cpp)
39
+ --api-key that server's key, if it wants one (OCTAFLOW_RUNNER_API_KEY)
40
+ --concurrency requests at once (default 1)
41
+ --check connect and report, then exit
42
+ --quiet log failures only
43
+ `;
44
+
45
+ /** The command line and the environment as one configuration, or the reason it is not one. */
46
+ export function readArgs(argv, env = process.env) {
47
+ let values;
48
+ try {
49
+ ({ values } = parseArgs({
50
+ args: argv,
51
+ options: {
52
+ url: { type: 'string' },
53
+ token: { type: 'string' },
54
+ ollama: { type: 'string' },
55
+ 'openai-compatible': { type: 'string' },
56
+ 'api-key': { type: 'string' },
57
+ concurrency: { type: 'string' },
58
+ check: { type: 'boolean', default: false },
59
+ quiet: { type: 'boolean', default: false },
60
+ help: { type: 'boolean', default: false },
61
+ },
62
+ strict: true,
63
+ allowPositionals: false,
64
+ }));
65
+ } catch (error) {
66
+ return { error: error instanceof Error ? error.message : String(error) };
67
+ }
68
+ if (values.help) return { help: true };
69
+ const url = values.url ?? env.OCTAFLOW_RUNNER_URL;
70
+ const token = values.token ?? env.OCTAFLOW_RUNNER_TOKEN;
71
+ if (!url) return { error: 'the door is missing: --url or OCTAFLOW_RUNNER_URL' };
72
+ if (!token) return { error: 'the token is missing: --token or OCTAFLOW_RUNNER_TOKEN' };
73
+ if (values.ollama && values['openai-compatible']) return { error: 'one backend: --ollama or --openai-compatible, not both' };
74
+ if (!values.ollama && !values['openai-compatible']) return { error: 'a backend is missing: --ollama <url> or --openai-compatible <url>' };
75
+ const concurrency = values.concurrency === undefined ? 1 : Number(values.concurrency);
76
+ if (!Number.isInteger(concurrency) || concurrency < 1) return { error: `--concurrency wants a whole number of 1 or more, not '${values.concurrency}'` };
77
+ return {
78
+ url,
79
+ token,
80
+ backend: values.ollama ? { kind: 'ollama', url: values.ollama } : { kind: 'openai-compatible', url: values['openai-compatible'], apiKey: values['api-key'] ?? env.OCTAFLOW_RUNNER_API_KEY },
81
+ concurrency,
82
+ check: values.check,
83
+ quiet: values.quiet,
84
+ };
85
+ }
86
+
87
+ /** The model ids the local server has, or `null` when it could not be asked. */
88
+ export async function installedModels(backend, fetchImpl = fetch) {
89
+ try {
90
+ if (backend.kind === 'ollama') {
91
+ const res = await fetchImpl(`${backend.url.replace(/\/+$/, '')}/api/tags`, { signal: AbortSignal.timeout(5000) });
92
+ if (!res.ok) return null;
93
+ const body = await res.json();
94
+ return (body.models ?? []).map((m) => m.name ?? m.model).filter(Boolean);
95
+ }
96
+ const res = await fetchImpl(`${backend.url.replace(/\/+$/, '')}/models`, {
97
+ headers: backend.apiKey ? { authorization: `Bearer ${backend.apiKey}` } : {},
98
+ signal: AbortSignal.timeout(5000),
99
+ });
100
+ if (!res.ok) return null;
101
+ const body = await res.json();
102
+ return (body.data ?? []).map((m) => m.id).filter(Boolean);
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /** Ollama lists `name:tag`; an app may declare `name` alone and mean `name:latest`. */
109
+ const sameModel = (declared, installed) => declared === installed || `${declared}:latest` === installed || declared === `${installed}:latest`;
110
+
111
+ /** The lines the start-up report prints — what the token is, what the app declares, what is there. */
112
+ export function startupReport(self, installed, backend) {
113
+ const lines = [`connected as ${self.id} (${self.label}) — tags ${self.tags.join(', ')}, up to ${self.concurrency} at once`];
114
+ if (self.models.length === 0) lines.push('the app declares no models for these tags yet — a provider row of kind `runner` with these tags names them');
115
+ for (const model of self.models) {
116
+ if (installed === null) lines.push(` ${model} — the local server could not be asked whether it is installed`);
117
+ else if (installed.some((i) => sameModel(model, i))) lines.push(` ${model} — installed`);
118
+ else lines.push(` ${model} — NOT installed${backend.kind === 'ollama' ? ` (ollama pull ${model})` : ''}`);
119
+ }
120
+ return lines;
121
+ }
122
+
123
+ async function makeBackend(backend) {
124
+ if (backend.kind === 'ollama') {
125
+ const { createOllama } = await import('../dist/ai/providers/index.js');
126
+ return createOllama({ baseUrl: backend.url });
127
+ }
128
+ let mod;
129
+ try {
130
+ mod = await import('@ai-sdk/openai-compatible');
131
+ } catch {
132
+ throw new Error("an OpenAI-compatible backend needs the optional peer '@ai-sdk/openai-compatible' installed beside octaflow");
133
+ }
134
+ const sdk = mod.createOpenAICompatible({ name: 'runner', baseURL: backend.url, ...(backend.apiKey ? { apiKey: backend.apiKey } : {}), supportsStructuredOutputs: true });
135
+ return {
136
+ languageModel: (id) => sdk.languageModel(id),
137
+ embeddingModel: (id) => sdk.embeddingModel(id),
138
+ };
139
+ }
140
+
141
+ export async function main(argv, env = process.env, io = { out: console.log, err: console.error }) {
142
+ const args = readArgs(argv, env);
143
+ if (args.help) {
144
+ io.out(USAGE);
145
+ return 0;
146
+ }
147
+ if (args.error) {
148
+ io.err(`octaflow-runner: ${args.error}\n\n${USAGE}`);
149
+ return 2;
150
+ }
151
+ const { createHttpRunnerClient, runModelRunner } = await import('../dist/ai/runner/index.js');
152
+ const client = createHttpRunnerClient({ url: args.url, token: args.token });
153
+
154
+ let self;
155
+ try {
156
+ self = await client.self();
157
+ } catch (error) {
158
+ io.err(`octaflow-runner: cannot reach the door at ${args.url}: ${error instanceof Error ? error.message : String(error)}`);
159
+ return 1;
160
+ }
161
+ const installed = await installedModels(args.backend);
162
+ for (const line of startupReport(self, installed, args.backend)) io.out(line);
163
+ if (installed === null) io.err(`octaflow-runner: no model server answered at ${args.backend.url} — requests will fail until it is up`);
164
+ if (args.check) return installed === null ? 1 : 0;
165
+
166
+ const backend = await makeBackend(args.backend);
167
+ const stop = new AbortController();
168
+ const onSignal = () => {
169
+ io.out('stopping — what is in flight is aborted');
170
+ stop.abort();
171
+ };
172
+ process.once('SIGINT', onSignal);
173
+ process.once('SIGTERM', onSignal);
174
+
175
+ io.out(`waiting for work from ${args.url}`);
176
+ await runModelRunner({
177
+ client,
178
+ backend,
179
+ concurrency: Math.min(args.concurrency, self.concurrency),
180
+ signal: stop.signal,
181
+ log: (event) => {
182
+ switch (event.type) {
183
+ case 'claimed':
184
+ if (!args.quiet) io.out(`→ ${event.request.id.slice(0, 8)} ${event.request.kind} ${event.request.model}`);
185
+ break;
186
+ case 'delivered':
187
+ if (!args.quiet || !event.ok) io.out(`${event.ok ? '✓' : '✗'} ${event.requestId.slice(0, 8)} in ${event.elapsedMs} ms`);
188
+ break;
189
+ case 'lost':
190
+ io.err(`✗ ${event.requestId.slice(0, 8)} no longer ours — cancelled by the caller, or the lease ran out`);
191
+ break;
192
+ case 'door_unreachable':
193
+ io.err(`door unreachable (${event.error instanceof Error ? event.error.message : String(event.error)}) — retrying in ${Math.round(event.retryInMs / 1000)} s`);
194
+ break;
195
+ }
196
+ },
197
+ });
198
+ return 0;
199
+ }
200
+
201
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
202
+ main(process.argv.slice(2)).then(
203
+ (code) => process.exit(code),
204
+ (error) => {
205
+ console.error(`octaflow-runner: ${error instanceof Error ? error.message : String(error)}`);
206
+ process.exit(1);
207
+ },
208
+ );
209
+ }
@@ -1,7 +1,7 @@
1
1
  import { b as OpenAiCompatibleProviderId, g as EmbeddingProviderId, i as AiEndpointResidency, o as AiProviderId } from "../../provider-registry-C_mOfdHH.js";
2
2
  import { A as maskKeys, C as ProviderErrorExplanation, D as explainProviderError, E as ProviderErrorPreset, O as isFatalProviderError, S as ProviderError, T as ProviderErrorPhrase, _ as ExplainProviderErrorOptions, a as OllamaProbeReport, c as ProviderProbeOptions, d as testProvider, f as CircuitBreaker, h as withCircuitBreaker, i as OllamaProbeOptions, k as isProviderError, l as ProviderProbeReport, m as createCircuitBreaker, o as ProbeableProvider, p as CircuitBreakerOptions, s as ProviderProbeFailure, u as testOllamaModel, v as FATAL_PROVIDER_ERROR_KEYS, w as ProviderErrorKey, x as PROVIDER_ERROR_STATUS, y as PROVIDER_ERROR_PHRASES } from "../../provider-probe-BDg4c1Rk.js";
3
3
  import { a as ModelCapabilities, c as ModelProvider, g as ModelProviders, m as ModelProviderRegion, o as ModelEntry } from "../../model-providers-DQU57vAf.js";
4
- import { u as ModelRunnerHub } from "../../hub-SXfvoqH3.js";
4
+ import { d as ModelRunnerHub } from "../../hub-CTf95SkP.js";
5
5
  import { EmbeddingModelV4, LanguageModelV4, LanguageModelV4CallOptions } from "@ai-sdk/provider";
6
6
  //#region src/ai/providers/ollama.d.ts
7
7
  /** `false` switches thinking off; a level asks a model that has levels for that much. */
@@ -1,7 +1,7 @@
1
1
  import { a as testProvider, i as testOllamaModel, o as createCircuitBreaker, s as withCircuitBreaker, u as createRefusalAwareModel } from "../../provider-probe-CwZWwFlo.js";
2
2
  import { a as EMBEDDING_PROVIDERS, c as OPENAI_COMPATIBLE_PROVIDER_ID, d as resolveEndpointResidency, t as AI_PROVIDERS } from "../../provider-registry-B91lgOmu.js";
3
3
  import { a as PROVIDER_ERROR_STATUS, c as isFatalProviderError, l as isProviderError, n as FATAL_PROVIDER_ERROR_KEYS, o as ProviderError, r as PROVIDER_ERROR_PHRASES, s as explainProviderError, u as maskKeys } from "../../provider-errors-DZWjl8di.js";
4
- import { t as createRunnerModels } from "../../model-DY-oJny8.js";
4
+ import { t as createRunnerModels } from "../../model-DztyiOGz.js";
5
5
  import { wrapLanguageModel } from "ai";
6
6
  import { APICallError } from "@ai-sdk/provider";
7
7
  //#region src/ai/providers/ollama.ts
@@ -1,5 +1,5 @@
1
- import { C as modelRunnerSelfSchema, S as modelRunnerInputSchema, _ as StoredModelRunnerRequest, a as ModelRunner, b as modelRunnerDeliverBodySchema, c as ModelRunnerHeartbeatBody, d as ModelRunnerRequestError, f as ModelRunnerRequestKind, g as StoredModelRunner, h as ModelRunnerStore, i as MODEL_RUNNER_TAG, l as ModelRunnerInput, m as ModelRunnerSelf, n as MODEL_RUNNER_REQUEST_KINDS, o as ModelRunnerClaimBody, p as ModelRunnerRequestStatus, r as MODEL_RUNNER_REQUEST_STATUSES, s as ModelRunnerDeliverBody, t as ClaimedModelRunnerRequest, u as ModelRunnerOutcome, v as createInMemoryModelRunnerStore, w as modelRunnerTagsSchema, x as modelRunnerHeartbeatBodySchema, y as modelRunnerClaimBodySchema } from "../../model-runner-wSk3p4m5.js";
2
- import { a as DEFAULT_MODEL_RUNNER_POLL_MS, c as ModelRunnerCall, d as ModelRunnerHubError, f as ModelRunnerHubOptions, h as hashModelRunnerToken, i as DEFAULT_MODEL_RUNNER_ONLINE_WINDOW_MS, l as ModelRunnerCallResult, m as generateModelRunnerToken, n as DEFAULT_MODEL_RUNNER_LEASE_MS, o as DEFAULT_MODEL_RUNNER_REQUEST_TIMEOUT_MS, p as createModelRunnerHub, r as DEFAULT_MODEL_RUNNER_MAX_ATTEMPTS, s as DEFAULT_MODEL_RUNNER_SWEEP_AFTER_MS, t as DEFAULT_MODEL_RUNNER_CLAIM_WAIT_MS, u as ModelRunnerHub } from "../../hub-SXfvoqH3.js";
1
+ import { C as modelRunnerDeliverBodySchema, D as modelRunnerTagsSchema, E as modelRunnerSelfSchema, S as modelRunnerClaimBodySchema, T as modelRunnerInputSchema, _ as StoredModelRunner, a as ModelRunner, b as createInMemoryModelRunnerStore, c as ModelRunnerDeliverBody, d as ModelRunnerOutcome, f as ModelRunnerRequestError, g as ModelRunnerStore, h as ModelRunnerSelf, i as MODEL_RUNNER_TAG, l as ModelRunnerHeartbeatBody, m as ModelRunnerRequestStatus, n as MODEL_RUNNER_REQUEST_KINDS, o as ModelRunnerChunkBody, p as ModelRunnerRequestKind, r as MODEL_RUNNER_REQUEST_STATUSES, s as ModelRunnerClaimBody, t as ClaimedModelRunnerRequest, u as ModelRunnerInput, v as StoredModelRunnerChunk, w as modelRunnerHeartbeatBodySchema, x as modelRunnerChunkBodySchema, y as StoredModelRunnerRequest } from "../../model-runner-2ONkybAq.js";
2
+ import { _ as generateModelRunnerToken, a as DEFAULT_MODEL_RUNNER_ONLINE_WINDOW_MS, c as DEFAULT_MODEL_RUNNER_SWEEP_AFTER_MS, d as ModelRunnerHub, f as ModelRunnerHubError, g as createModelRunnerHub, h as StartedModelRunnerCall, i as DEFAULT_MODEL_RUNNER_MAX_ATTEMPTS, l as ModelRunnerCall, m as ModelRunnerStart, n as DEFAULT_MODEL_RUNNER_CLAIM_WAIT_MS, o as DEFAULT_MODEL_RUNNER_POLL_MS, p as ModelRunnerHubOptions, r as DEFAULT_MODEL_RUNNER_LEASE_MS, s as DEFAULT_MODEL_RUNNER_REQUEST_TIMEOUT_MS, t as DEFAULT_MODEL_RUNNER_CHUNK_BATCH, u as ModelRunnerCallResult, v as hashModelRunnerToken } from "../../hub-CTf95SkP.js";
3
3
  import { EmbeddingModelV4, EmbeddingModelV4CallOptions, EmbeddingModelV4Result, LanguageModelV4, LanguageModelV4CallOptions, LanguageModelV4GenerateResult, LanguageModelV4StreamPart } from "@ai-sdk/provider";
4
4
  //#region src/ai/runner/model.d.ts
5
5
  export interface RunnerModelsOptions {
@@ -33,6 +33,10 @@ export interface ModelRunnerClient {
33
33
  held: string[];
34
34
  }>;
35
35
  deliver(requestId: string, outcome: ModelRunnerOutcome): Promise<boolean>;
36
+ /** One batch of a `stream` request's parts; `accepted: false` says to stop. */
37
+ chunk(requestId: string, seq: number, parts: unknown[]): Promise<{
38
+ accepted: boolean;
39
+ }>;
36
40
  }
37
41
  /** What runs the requests: a model per id, settings as the request's entry carries them. */
38
42
  export interface ModelRunnerBackend {
@@ -53,6 +57,10 @@ export interface RunModelRunnerOptions {
53
57
  /** The first wait after the door could not be reached; doubles up to `maxBackoffMs`. Default 1 s / 30 s. */
54
58
  backoffMs?: number;
55
59
  maxBackoffMs?: number;
60
+ /** A stream's parts are posted at least this often while some are waiting. Default 100 ms. */
61
+ chunkEveryMs?: number;
62
+ /** … or as soon as this many are waiting. Default 64. */
63
+ chunkParts?: number;
56
64
  /** Told about each request and each failure to reach the door. */
57
65
  log?: (event: ModelRunnerLogEvent) => void;
58
66
  }
@@ -72,8 +80,21 @@ export type ModelRunnerLogEvent = {
72
80
  error: unknown;
73
81
  retryInMs: number;
74
82
  };
83
+ /** Where a stream's chunks go: the door, or a test. `false` back means stop. */
84
+ export type ChunkSink = (seq: number, parts: unknown[]) => Promise<boolean>;
85
+ export interface RunRequestOptions {
86
+ signal?: AbortSignal;
87
+ /** For a `stream` request: where its chunks go. Without one, the stream is collected and answered whole. */
88
+ chunks?: ChunkSink;
89
+ chunkEveryMs?: number;
90
+ chunkParts?: number;
91
+ }
92
+ /** The answer a runner delivers for a stream it posted chunk by chunk: how many chunks there were. */
93
+ export interface StreamedResult {
94
+ streamed: number;
95
+ }
75
96
  /** Run one claimed request on the backend; never throws — a failure is the outcome. */
76
- export declare function runModelRunnerRequest(backend: ModelRunnerBackend, request: ClaimedModelRunnerRequest, signal?: AbortSignal): Promise<ModelRunnerOutcome>;
97
+ export declare function runModelRunnerRequest(backend: ModelRunnerBackend, request: ClaimedModelRunnerRequest, options?: RunRequestOptions | AbortSignal): Promise<ModelRunnerOutcome>;
77
98
  /** The loop. Returns when `signal` aborts. */
78
99
  export declare function runModelRunner(options: RunModelRunnerOptions): Promise<void>;
79
100
  /** The hub in the same process as the client — a host that runs its runner inside, and tests. */
@@ -84,7 +105,7 @@ export interface HttpRunnerClientOptions {
84
105
  token: string;
85
106
  fetch?: typeof fetch;
86
107
  }
87
- /** The door over HTTP: `createModelRunnerHandler`'s four routes. Throws on a door that cannot be reached or answers an error. */
108
+ /** The door over HTTP: `createModelRunnerHandler`'s five routes. Throws on a door that cannot be reached or answers an error. */
88
109
  export declare function createHttpRunnerClient(options: HttpRunnerClientOptions): ModelRunnerClient;
89
110
  //#endregion
90
111
  //#region src/ai/runner/wire.d.ts
@@ -106,9 +127,12 @@ export declare function encodeCallOptions(options: LanguageModelV4CallOptions):
106
127
  export declare function decodeCallOptions(wire: unknown): LanguageModelV4CallOptions;
107
128
  export declare function encodeGenerateResult(result: LanguageModelV4GenerateResult): unknown;
108
129
  export declare function decodeGenerateResult(wire: unknown): LanguageModelV4GenerateResult;
130
+ /** Stream parts as JSON: bytes as base64, URLs as strings; an `error` part carries a sentence, since an `Error` does not cross. */
131
+ export declare function encodeStreamParts(parts: LanguageModelV4StreamPart[]): unknown[];
132
+ export declare function decodeStreamParts(wire: unknown[]): LanguageModelV4StreamPart[];
109
133
  export declare function encodeEmbedOptions(options: EmbeddingModelV4CallOptions): EmbedPayload['options'];
110
134
  export declare function decodeEmbedOptions(wire: unknown): EmbeddingModelV4CallOptions;
111
135
  export declare function encodeEmbedResult(result: EmbeddingModelV4Result): unknown;
112
136
  export declare function decodeEmbedResult(wire: unknown): EmbeddingModelV4Result;
113
137
  //#endregion
114
- export { ClaimedModelRunnerRequest, DEFAULT_MODEL_RUNNER_CLAIM_WAIT_MS, DEFAULT_MODEL_RUNNER_LEASE_MS, DEFAULT_MODEL_RUNNER_MAX_ATTEMPTS, DEFAULT_MODEL_RUNNER_ONLINE_WINDOW_MS, DEFAULT_MODEL_RUNNER_POLL_MS, DEFAULT_MODEL_RUNNER_REQUEST_TIMEOUT_MS, DEFAULT_MODEL_RUNNER_SWEEP_AFTER_MS, MODEL_RUNNER_REQUEST_KINDS, MODEL_RUNNER_REQUEST_STATUSES, MODEL_RUNNER_TAG, ModelRunner, ModelRunnerCall, ModelRunnerCallResult, ModelRunnerClaimBody, ModelRunnerDeliverBody, ModelRunnerHeartbeatBody, ModelRunnerHub, ModelRunnerHubError, ModelRunnerHubOptions, ModelRunnerInput, ModelRunnerOutcome, ModelRunnerRequestError, ModelRunnerRequestKind, ModelRunnerRequestStatus, ModelRunnerSelf, ModelRunnerStore, StoredModelRunner, StoredModelRunnerRequest, createInMemoryModelRunnerStore, createModelRunnerHub, generateModelRunnerToken, hashModelRunnerToken, modelRunnerClaimBodySchema, modelRunnerDeliverBodySchema, modelRunnerHeartbeatBodySchema, modelRunnerInputSchema, modelRunnerSelfSchema, modelRunnerTagsSchema };
138
+ export { ClaimedModelRunnerRequest, DEFAULT_MODEL_RUNNER_CHUNK_BATCH, DEFAULT_MODEL_RUNNER_CLAIM_WAIT_MS, DEFAULT_MODEL_RUNNER_LEASE_MS, DEFAULT_MODEL_RUNNER_MAX_ATTEMPTS, DEFAULT_MODEL_RUNNER_ONLINE_WINDOW_MS, DEFAULT_MODEL_RUNNER_POLL_MS, DEFAULT_MODEL_RUNNER_REQUEST_TIMEOUT_MS, DEFAULT_MODEL_RUNNER_SWEEP_AFTER_MS, MODEL_RUNNER_REQUEST_KINDS, MODEL_RUNNER_REQUEST_STATUSES, MODEL_RUNNER_TAG, ModelRunner, ModelRunnerCall, ModelRunnerCallResult, ModelRunnerChunkBody, ModelRunnerClaimBody, ModelRunnerDeliverBody, ModelRunnerHeartbeatBody, ModelRunnerHub, ModelRunnerHubError, ModelRunnerHubOptions, ModelRunnerInput, ModelRunnerOutcome, ModelRunnerRequestError, ModelRunnerRequestKind, ModelRunnerRequestStatus, ModelRunnerSelf, ModelRunnerStart, ModelRunnerStore, StartedModelRunnerCall, StoredModelRunner, StoredModelRunnerChunk, StoredModelRunnerRequest, createInMemoryModelRunnerStore, createModelRunnerHub, generateModelRunnerToken, hashModelRunnerToken, modelRunnerChunkBodySchema, modelRunnerClaimBodySchema, modelRunnerDeliverBodySchema, modelRunnerHeartbeatBodySchema, modelRunnerInputSchema, modelRunnerSelfSchema, modelRunnerTagsSchema };