@almadar/llm 2.56.0 → 2.57.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/dist/{client-DfzMDgkm.d.ts → client-4Zrj248l.d.ts} +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/index.d.ts +115 -4
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/contracts.ts +62 -0
- package/src/index.ts +12 -0
- package/src/scripted-tool-client.ts +49 -0
|
@@ -514,4 +514,4 @@ declare function createOpenRouterClient(options?: Partial<Omit<LLMClientOptions,
|
|
|
514
514
|
*/
|
|
515
515
|
declare function createZhipuClient(options?: Partial<Omit<LLMClientOptions, 'provider'>>): LLMClient;
|
|
516
516
|
|
|
517
|
-
export { ANTHROPIC_MODELS as A, createKimiClient as B, type
|
|
517
|
+
export { ANTHROPIC_MODELS as A, createKimiClient as B, type ChatCompletionMessage as C, DEEPSEEK_MODELS as D, createOpenAIClient as E, createOpenRouterClient as F, createRequirementsClient as G, createZhipuClient as H, getAvailableProvider as I, getSharedLLMClient as J, KIMI_MODELS as K, type LLMFinishReason as L, isProviderAvailable as M, listLmstudioModels as N, OPENAI_MODELS as O, PROVIDER_CATALOG as P, listSelectableProviders as Q, parseChatCompletionResponse as R, resetSharedLLMClient as S, type VisionCallOptions as V, LLMClient as a, type ChatCompletionToolDef as b, type LLMUsage as c, type CacheAwareLLMCallOptions as d, type CacheableBlock as e, type ChatCompletionChoice as f, type ChatCompletionResponse as g, type ChatCompletionRole as h, type ChatCompletionToolCall as i, type ChatCompletionUsage as j, type LLMCallOptions as k, type LLMClientOptions as l, type LLMProvider as m, type LLMResponse as n, type LLMStreamChunk as o, type LLMStreamOptions as p, OPENROUTER_MODELS as q, type ProviderCatalogEntry as r, type ProviderConfig as s, type VisionImageMediaType as t, type VisionImagePart as u, buildVisionMessageContent as v, createAnthropicClient as w, createCreativeClient as x, createDeepSeekClient as y, createFixClient as z };
|
package/dist/client.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ import '@langchain/openai';
|
|
|
3
3
|
import '@langchain/anthropic';
|
|
4
4
|
import '@anthropic-ai/sdk';
|
|
5
5
|
import 'zod';
|
|
6
|
-
export { A as ANTHROPIC_MODELS,
|
|
6
|
+
export { A as ANTHROPIC_MODELS, d as CacheAwareLLMCallOptions, e as CacheableBlock, D as DEEPSEEK_MODELS, K as KIMI_MODELS, k as LLMCallOptions, a as LLMClient, l as LLMClientOptions, L as LLMFinishReason, m as LLMProvider, n as LLMResponse, o as LLMStreamChunk, p as LLMStreamOptions, c as LLMUsage, O as OPENAI_MODELS, q as OPENROUTER_MODELS, P as PROVIDER_CATALOG, r as ProviderCatalogEntry, s as ProviderConfig, V as VisionCallOptions, t as VisionImageMediaType, u as VisionImagePart, v as buildVisionMessageContent, w as createAnthropicClient, x as createCreativeClient, y as createDeepSeekClient, z as createFixClient, B as createKimiClient, E as createOpenAIClient, F as createOpenRouterClient, G as createRequirementsClient, H as createZhipuClient, I as getAvailableProvider, J as getSharedLLMClient, M as isProviderAvailable, N as listLmstudioModels, Q as listSelectableProviders, S as resetSharedLLMClient } from './client-4Zrj248l.js';
|
|
7
7
|
import '@almadar/core';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { L as LLMFinishReason, a as LLMClient } from './client-
|
|
2
|
-
export { A as ANTHROPIC_MODELS,
|
|
1
|
+
import { L as LLMFinishReason, a as LLMClient, C as ChatCompletionMessage, b as ChatCompletionToolDef, c as LLMUsage } from './client-4Zrj248l.js';
|
|
2
|
+
export { A as ANTHROPIC_MODELS, d as CacheAwareLLMCallOptions, e as CacheableBlock, f as ChatCompletionChoice, g as ChatCompletionResponse, h as ChatCompletionRole, i as ChatCompletionToolCall, j as ChatCompletionUsage, D as DEEPSEEK_MODELS, K as KIMI_MODELS, k as LLMCallOptions, l as LLMClientOptions, m as LLMProvider, n as LLMResponse, o as LLMStreamChunk, p as LLMStreamOptions, O as OPENAI_MODELS, q as OPENROUTER_MODELS, P as PROVIDER_CATALOG, r as ProviderCatalogEntry, s as ProviderConfig, V as VisionCallOptions, t as VisionImageMediaType, u as VisionImagePart, v as buildVisionMessageContent, w as createAnthropicClient, x as createCreativeClient, y as createDeepSeekClient, z as createFixClient, B as createKimiClient, E as createOpenAIClient, F as createOpenRouterClient, G as createRequirementsClient, H as createZhipuClient, I as getAvailableProvider, J as getSharedLLMClient, M as isProviderAvailable, N as listLmstudioModels, Q as listSelectableProviders, R as parseChatCompletionResponse, S as resetSharedLLMClient } from './client-4Zrj248l.js';
|
|
3
3
|
export { E as EstimateCostTokens, a as RateLimiter, R as RateLimiterOptions, b as TokenTracker, T as TokenUsage, e as estimateCostUSD, g as getGlobalRateLimiter, c as getGlobalTokenTracker, r as resetGlobalRateLimiter, d as resetGlobalTokenTracker } from './rate-limiter-Bz0iSJgZ.js';
|
|
4
|
-
import { JsonValue, ServiceParams, ServiceContract } from '@almadar/core';
|
|
4
|
+
import { JsonValue, ServiceParams, ServiceContract, ServiceParamsValue } from '@almadar/core';
|
|
5
5
|
export { autoCloseJson, extractJsonFromText, isValidJson, parseJsonResponse, safeParseJson } from './json-parser.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
export { JsonSchema, STRUCTURED_OUTPUT_MODELS, StructuredGenerationOptions, StructuredGenerationResult, StructuredOutputClient, StructuredOutputOptions, getStructuredOutputClient, isStructuredOutputAvailable, resetStructuredOutputClient } from './structured-output.js';
|
|
@@ -379,6 +379,33 @@ type ExtractedValue = string | number | boolean | null | ExtractedValue[] | {
|
|
|
379
379
|
[key: string]: ExtractedValue;
|
|
380
380
|
};
|
|
381
381
|
type ExtractedData = Record<string, ExtractedValue>;
|
|
382
|
+
/** One turn of the conversation `call-tools` continues. */
|
|
383
|
+
interface AgentChatMessage extends ServiceParams {
|
|
384
|
+
role: "user" | "assistant";
|
|
385
|
+
content: string;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* What the model may use in `call-tools`. Exactly one of:
|
|
389
|
+
* `event` — a declared external input, addressed like a `listens` source
|
|
390
|
+
* (`EVENT`, `Trait.EVENT`, `Orbital.Trait.EVENT`), fired as the caller;
|
|
391
|
+
* `read` — an entity whose rows the caller may read.
|
|
392
|
+
*/
|
|
393
|
+
interface AgentTool extends ServiceParams {
|
|
394
|
+
event?: string;
|
|
395
|
+
read?: string;
|
|
396
|
+
}
|
|
397
|
+
/** One tool use inside a `call-tools` run, in order. */
|
|
398
|
+
interface AgentStep extends ServiceParams {
|
|
399
|
+
/** `input` fired a declared input, `read` read an entity, `invalid` named nothing the tools allow. */
|
|
400
|
+
kind: "input" | "read" | "invalid";
|
|
401
|
+
/** The input address or entity name the model asked for. */
|
|
402
|
+
target: string;
|
|
403
|
+
/** The arguments the model gave, as JSON text. */
|
|
404
|
+
args: string;
|
|
405
|
+
ok: boolean;
|
|
406
|
+
/** Why it did not succeed: the rejection code, failed guard or error. */
|
|
407
|
+
detail?: string;
|
|
408
|
+
}
|
|
382
409
|
/**
|
|
383
410
|
* All call-service actions exposed by the LLM service.
|
|
384
411
|
*/
|
|
@@ -459,6 +486,37 @@ type LLMServiceActions = {
|
|
|
459
486
|
keyPoints: string[];
|
|
460
487
|
};
|
|
461
488
|
};
|
|
489
|
+
/**
|
|
490
|
+
* Answer a request by operating the running app as the caller: the model may
|
|
491
|
+
* fire the declared inputs and read the entities named in `tools`, through the
|
|
492
|
+
* host's ordinary dispatch (guards, policies and listens all apply), until it
|
|
493
|
+
* replies. Hitting `maxSteps` is a failure.
|
|
494
|
+
* @synonyms agent, assistant, tool use, act
|
|
495
|
+
*/
|
|
496
|
+
"call-tools": {
|
|
497
|
+
params: {
|
|
498
|
+
/** The conversation so far, oldest first; the last turn is the request. */
|
|
499
|
+
messages: AgentChatMessage[];
|
|
500
|
+
/** The inputs and entities the model may use. */
|
|
501
|
+
tools: AgentTool[];
|
|
502
|
+
/** Standing instructions for the model. */
|
|
503
|
+
instructions?: string;
|
|
504
|
+
/** Model round-trips allowed before the call fails (default 8). */
|
|
505
|
+
maxSteps?: number;
|
|
506
|
+
/** Model id override. */
|
|
507
|
+
model?: string;
|
|
508
|
+
};
|
|
509
|
+
result: {
|
|
510
|
+
reply: string;
|
|
511
|
+
steps: AgentStep[];
|
|
512
|
+
usage: {
|
|
513
|
+
promptTokens: number;
|
|
514
|
+
completionTokens: number;
|
|
515
|
+
totalTokens: number;
|
|
516
|
+
modelCalls: number;
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
};
|
|
462
520
|
/** Generate embeddings for an array of texts. */
|
|
463
521
|
embed: {
|
|
464
522
|
params: {
|
|
@@ -478,5 +536,58 @@ type LLMServiceActions = {
|
|
|
478
536
|
* that dispatches to the correct action handler.
|
|
479
537
|
*/
|
|
480
538
|
type LLMServiceContract = ServiceContract<LLMServiceActions>;
|
|
539
|
+
/**
|
|
540
|
+
* All call-service actions exposed by the ML service — model inference on a
|
|
541
|
+
* trained or off-the-shelf checkpoint. Reached via a deployed serving
|
|
542
|
+
* orbital (see `@almadar/integrations`' `ml` integration).
|
|
543
|
+
*/
|
|
544
|
+
type MLServiceActions = {
|
|
545
|
+
/** Run inference against a deployed model checkpoint. */
|
|
546
|
+
infer: {
|
|
547
|
+
params: {
|
|
548
|
+
/** `hf:<id>` for off-the-shelf weights, or a masar checkpoint id. */
|
|
549
|
+
model: string;
|
|
550
|
+
/** JSON-safe model input (tensor-shaped payload from `contract/entity-to-tensor`). */
|
|
551
|
+
input: ServiceParamsValue;
|
|
552
|
+
};
|
|
553
|
+
result: {
|
|
554
|
+
/** JSON-safe model output, before contract clamp/validation by the caller. */
|
|
555
|
+
output: ServiceParamsValue;
|
|
556
|
+
confidence: number;
|
|
557
|
+
/** Contract violations reported by the model service, if any (see `contract/violations`). */
|
|
558
|
+
violations: ServiceParamsValue[];
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
};
|
|
562
|
+
/**
|
|
563
|
+
* The full service contract for the ML service.
|
|
564
|
+
* Implementations must provide an `execute(action, params)` method
|
|
565
|
+
* that dispatches to the correct action handler.
|
|
566
|
+
*/
|
|
567
|
+
type MLServiceContract = ServiceContract<MLServiceActions>;
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* The tool-calling seam a tool loop needs, and a scripted model for it.
|
|
571
|
+
*
|
|
572
|
+
* `ToolCallingClient` is the one `LLMClient` capability a tool loop uses: one
|
|
573
|
+
* `callWithTools` round-trip. `createScriptedToolClient` replays scripted
|
|
574
|
+
* assistant turns in order, so a loop runs deterministically with no network
|
|
575
|
+
* (unit tests, the verifiers' agent checks). It records every request it gets.
|
|
576
|
+
*
|
|
577
|
+
* @packageDocumentation
|
|
578
|
+
*/
|
|
579
|
+
|
|
580
|
+
type ToolCallingClient = Pick<LLMClient, 'callWithTools'>;
|
|
581
|
+
interface ScriptedTurn {
|
|
582
|
+
message: ChatCompletionMessage;
|
|
583
|
+
usage?: LLMUsage;
|
|
584
|
+
}
|
|
585
|
+
interface ScriptedToolClient extends ToolCallingClient {
|
|
586
|
+
readonly requests: ReadonlyArray<{
|
|
587
|
+
messages: ReadonlyArray<ChatCompletionMessage>;
|
|
588
|
+
tools: ReadonlyArray<ChatCompletionToolDef>;
|
|
589
|
+
}>;
|
|
590
|
+
}
|
|
591
|
+
declare function createScriptedToolClient(turns: readonly ScriptedTurn[]): ScriptedToolClient;
|
|
481
592
|
|
|
482
|
-
export { type ContinuationOptions, type ContinuationResult, type EmbeddingBatchResult, EmbeddingClient, type EmbeddingClientOptions, type EmbeddingProvider, type EmbeddingResult, type EmbeddingUsage, type GeneratedImage, IMAGE_PROVIDER_CATALOG, ImageClient, type ImageClientOptions, type ImageGenerateOptions, type ImageGenerateResult, type ImageModelCapabilities, type ImageProvider, type ImageReference, LLMClient, LLMFinishReason, type LLMServiceActions, type LLMServiceContract, LOCAL_IMAGE_DEFAULTS, LOCAL_IMAGE_MODELS, OPENROUTER_IMAGE_DEFAULTS, OPENROUTER_IMAGE_MODELS, type TruncationReason, type TruncationResult, buildGenericContinuationPrompt, callWithContinuation, cosineSimilarity, detectTruncation, findLastCompleteElement, isLikelyTruncated, mergeResponses, resolveDefaultEmbeddingProvider, resolveDefaultImageProvider, salvagePartialResponse };
|
|
593
|
+
export { type AgentChatMessage, type AgentStep, type AgentTool, ChatCompletionMessage, ChatCompletionToolDef, type ContinuationOptions, type ContinuationResult, type EmbeddingBatchResult, EmbeddingClient, type EmbeddingClientOptions, type EmbeddingProvider, type EmbeddingResult, type EmbeddingUsage, type GeneratedImage, IMAGE_PROVIDER_CATALOG, ImageClient, type ImageClientOptions, type ImageGenerateOptions, type ImageGenerateResult, type ImageModelCapabilities, type ImageProvider, type ImageReference, LLMClient, LLMFinishReason, type LLMServiceActions, type LLMServiceContract, LLMUsage, LOCAL_IMAGE_DEFAULTS, LOCAL_IMAGE_MODELS, type MLServiceActions, type MLServiceContract, OPENROUTER_IMAGE_DEFAULTS, OPENROUTER_IMAGE_MODELS, type ScriptedToolClient, type ScriptedTurn, type ToolCallingClient, type TruncationReason, type TruncationResult, buildGenericContinuationPrompt, callWithContinuation, cosineSimilarity, createScriptedToolClient, detectTruncation, findLastCompleteElement, isLikelyTruncated, mergeResponses, resolveDefaultEmbeddingProvider, resolveDefaultImageProvider, salvagePartialResponse };
|
package/dist/index.js
CHANGED
|
@@ -958,6 +958,26 @@ ${partialResponse}
|
|
|
958
958
|
|
|
959
959
|
Continue generating now:`;
|
|
960
960
|
}
|
|
961
|
+
|
|
962
|
+
// src/scripted-tool-client.ts
|
|
963
|
+
function createScriptedToolClient(turns) {
|
|
964
|
+
const requests = [];
|
|
965
|
+
return {
|
|
966
|
+
requests,
|
|
967
|
+
async callWithTools(options) {
|
|
968
|
+
const turn = turns[requests.length];
|
|
969
|
+
requests.push({ messages: [...options.messages], tools: [...options.tools] });
|
|
970
|
+
if (turn === void 0) {
|
|
971
|
+
throw new Error(`scripted tool client: no scripted turn ${requests.length} (script has ${turns.length})`);
|
|
972
|
+
}
|
|
973
|
+
return {
|
|
974
|
+
message: turn.message,
|
|
975
|
+
finishReason: turn.message.tool_calls && turn.message.tool_calls.length > 0 ? "tool_calls" : "stop",
|
|
976
|
+
usage: turn.usage ?? null
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
}
|
|
961
981
|
export {
|
|
962
982
|
ANTHROPIC_MODELS,
|
|
963
983
|
DEEPSEEK_MODELS,
|
|
@@ -996,6 +1016,7 @@ export {
|
|
|
996
1016
|
createOpenAIClient,
|
|
997
1017
|
createOpenRouterClient,
|
|
998
1018
|
createRequirementsClient,
|
|
1019
|
+
createScriptedToolClient,
|
|
999
1020
|
createZhipuClient,
|
|
1000
1021
|
detectTruncation,
|
|
1001
1022
|
estimateCostUSD,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/embedding-client.ts","../src/image-client.ts","../src/truncation-detector.ts","../src/continuation.ts"],"sourcesContent":["/**\n * Embedding Client\n *\n * Single-purpose client for text embeddings. Used by `@almadar-io/agent`'s\n * cosine-similarity catalog retrieval (rank organisms + atoms against the\n * user's request before rendering Stage A's prompt) and by\n * `@almadar/std`'s publish-time embedding bake step.\n *\n * Providers:\n * - `openai` (default model `text-embedding-3-small`, 1536-d) — requires\n * `OPENAI_API_KEY`.\n * - `openrouter` (default model `baai/bge-base-en-v1.5`, 768-d) — requires\n * `OPEN_ROUTER_API_KEY`. Same OpenAI-compatible request shape, just a\n * different base URL.\n * - `lmstudio` (default model `text-embedding-bge-base-en-v1.5`, same\n * bge-base-en-v1.5 weights as the openrouter default, run locally) —\n * local LM Studio server, no API key. Mirrors `client.ts`'s\n * `PROVIDER_CONFIGS.lmstudio`: base URL from `LMSTUDIO_URL` (default\n * `http://127.0.0.1:1234/v1`), model from `LMSTUDIO_EMBEDDING_MODEL`.\n *\n * All three providers return the same response envelope (`{data:[{embedding,index}]}`),\n * so the request/response code is shared.\n *\n * @packageDocumentation\n */\n\nexport type EmbeddingProvider = 'openai' | 'openrouter' | 'lmstudio';\n\nconst EMBEDDING_PROVIDERS: readonly EmbeddingProvider[] = ['openai', 'openrouter', 'lmstudio'];\n\n/**\n * Canonical embedding-provider resolution — the ONE place every downstream\n * consumer should defer to instead of hardcoding a literal provider.\n * Mirrors the `EMBEDDING_PROVIDER` env var `@almadar-io/integrations`'s\n * `RuntimeIntegrationManager`/`getEmbeddings()` already reads (its own\n * `?? 'openrouter'` fallback is the precedent this centralizes).\n *\n * Defaults to `openrouter` — UNCHANGED from every existing deployment's\n * behavior — so nothing (cloud/server included) switches to a local-only\n * provider unless an operator explicitly sets `EMBEDDING_PROVIDER=lmstudio`\n * in that specific environment (e.g. a dev machine running LM Studio).\n * Consumers: `@almadar-io/rabit`'s catalog narrower\n * (`runtime/services/embedding.ts`) and `@almadar/workspace`'s index\n * embedder (`workspace-index/embedder.ts`).\n */\nexport function resolveDefaultEmbeddingProvider(): EmbeddingProvider {\n const v = process.env.EMBEDDING_PROVIDER;\n return v && (EMBEDDING_PROVIDERS as readonly string[]).includes(v) ? (v as EmbeddingProvider) : 'openrouter';\n}\n\nexport interface EmbeddingClientOptions {\n provider: EmbeddingProvider;\n /** Defaults: openai → text-embedding-3-small, openrouter → baai/bge-base-en-v1.5, lmstudio → text-embedding-bge-base-en-v1.5. */\n model?: string;\n /** Override API key. Defaults to provider-specific env var; lmstudio needs none. */\n apiKey?: string;\n /** Override base URL. Defaults to provider canonical endpoint (lmstudio: `LMSTUDIO_URL`). */\n baseUrl?: string;\n /** Override the fetch implementation (for tests). Defaults to global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface EmbeddingUsage {\n promptTokens: number;\n totalTokens: number;\n}\n\nexport interface EmbeddingResult {\n embedding: readonly number[];\n usage: EmbeddingUsage;\n}\n\nexport interface EmbeddingBatchResult {\n embeddings: readonly (readonly number[])[];\n usage: EmbeddingUsage;\n}\n\ninterface OpenAIEmbeddingApiResponse {\n data: Array<{ embedding: number[]; index: number }>;\n usage?: { prompt_tokens?: number; total_tokens?: number };\n /** OpenRouter reports provider hiccups as HTTP 200 with an error envelope and no `data`. */\n error?: { message?: string; code?: number | string };\n}\n\n/**\n * Thrown by `embedBatchOnce` on a non-OK HTTP response. Carries the status\n * code (and any `Retry-After` hint) as structured fields so the retry loop\n * can react to a specific status — a 429 in particular — without\n * string-matching the error message.\n */\nclass EmbeddingHttpError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly retryAfterMs?: number,\n ) {\n super(message);\n this.name = 'EmbeddingHttpError';\n }\n}\n\n/** Transient-failure retry policy: attempts and backoff (ms) between them. */\nconst EMBED_MAX_ATTEMPTS = 3;\nconst EMBED_RETRY_BACKOFF_MS = [500, 2000];\n\n/**\n * HTTP 429 gets its own, longer retry budget. A rate limit (\"model busy,\n * retry later\") is the single most retryable failure there is, but it\n * rarely clears within the generic transient-failure window above — the\n * provider is telling you to wait, sometimes via `Retry-After`, which this\n * honors when present instead of guessing.\n */\nconst EMBED_RATE_LIMIT_MAX_ATTEMPTS = 4;\nconst EMBED_RATE_LIMIT_BACKOFF_MS = [2000, 5000, 15000];\n\n/** Provider rejects an input that exceeds the model's context window (bge: 512 tokens). */\nconst TOKEN_OVERFLOW_RE =\n /context length|maximum input length|reduce the length of the input|input_tokens/i;\n\nconst DEFAULT_MODELS: Record<EmbeddingProvider, string> = {\n openai: 'text-embedding-3-small',\n openrouter: 'baai/bge-base-en-v1.5',\n lmstudio: 'text-embedding-bge-base-en-v1.5',\n};\n\nconst DEFAULT_BASE_URLS: Record<EmbeddingProvider, string> = {\n openai: 'https://api.openai.com/v1',\n openrouter: 'https://openrouter.ai/api/v1',\n lmstudio: 'http://127.0.0.1:1234/v1',\n};\n\nconst API_KEY_ENV_VARS: Partial<Record<EmbeddingProvider, string>> = {\n openai: 'OPENAI_API_KEY',\n openrouter: 'OPEN_ROUTER_API_KEY',\n};\n\nexport class EmbeddingClient {\n private readonly provider: EmbeddingProvider;\n private readonly model: string;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: EmbeddingClientOptions) {\n this.provider = opts.provider;\n this.model = opts.model ?? (this.provider === 'lmstudio' ? process.env.LMSTUDIO_EMBEDDING_MODEL ?? DEFAULT_MODELS.lmstudio : DEFAULT_MODELS[this.provider]);\n const defaultBaseUrl = this.provider === 'lmstudio' ? process.env.LMSTUDIO_URL ?? DEFAULT_BASE_URLS.lmstudio : DEFAULT_BASE_URLS[this.provider];\n this.baseUrl = (opts.baseUrl ?? defaultBaseUrl).replace(/\\/$/, '');\n this.fetchImpl = opts.fetchImpl ?? fetch;\n\n if (this.provider === 'lmstudio') {\n // Local LM Studio server — no credential to check, mirrors client.ts's PROVIDER_CONFIGS.lmstudio.\n this.apiKey = opts.apiKey ?? 'not-needed';\n return;\n }\n const apiKey = opts.apiKey ?? this.resolveApiKey();\n if (!apiKey) {\n throw new Error(\n `EmbeddingClient: API key for provider '${this.provider}' not found. ` +\n `Set ${API_KEY_ENV_VARS[this.provider]} in your environment or pass options.apiKey.`,\n );\n }\n this.apiKey = apiKey;\n }\n\n /** Embed a single text. */\n async embed(text: string): Promise<EmbeddingResult> {\n const batch = await this.embedBatch([text]);\n const embedding = batch.embeddings[0];\n if (!embedding) {\n throw new Error('EmbeddingClient.embed: provider returned no embedding');\n }\n return { embedding, usage: batch.usage };\n }\n\n /**\n * Embed a batch of texts. Used by `@almadar/std`'s publish-time\n * `build-embeddings.ts` script — one batch call for the full catalog\n * (≈190 entries) rather than per-entry requests.\n */\n async embedBatch(texts: ReadonlyArray<string>): Promise<EmbeddingBatchResult> {\n if (texts.length === 0) {\n return { embeddings: [], usage: { promptTokens: 0, totalTokens: 0 } };\n }\n // bge-base-en-v1.5 has a 512-token context limit. Chars-per-token varies\n // with density (~4 for typical English, ~2.5-3 for dense technical/code\n // text), so no fixed char cap can guarantee a fit. Start at 2000 chars and,\n // when the provider rejects an input for exceeding the context window,\n // halve the cap and retry — the provider's own token count is the source\n // of truth, not a chars-per-token guess.\n let charCap = 2000;\n let lastError: Error = new Error('EmbeddingClient: unreachable');\n let genericAttempt = 0;\n let rateLimitAttempt = 0;\n for (;;) {\n const trimmed = texts.map((t) => (t.length > charCap ? t.slice(0, charCap) : t));\n try {\n return await this.embedBatchOnce(texts.length, trimmed);\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n if (TOKEN_OVERFLOW_RE.test(lastError.message) && charCap > 500) {\n charCap = Math.floor(charCap / 2);\n continue;\n }\n if (lastError instanceof EmbeddingHttpError && lastError.status === 429) {\n if (rateLimitAttempt >= EMBED_RATE_LIMIT_MAX_ATTEMPTS - 1) break;\n const wait = lastError.retryAfterMs ?? EMBED_RATE_LIMIT_BACKOFF_MS[rateLimitAttempt];\n rateLimitAttempt++;\n await new Promise((r) => setTimeout(r, wait));\n continue;\n }\n genericAttempt++;\n const backoff = EMBED_RETRY_BACKOFF_MS[genericAttempt - 1];\n if (genericAttempt >= EMBED_MAX_ATTEMPTS || backoff === undefined) break;\n await new Promise((r) => setTimeout(r, backoff));\n }\n }\n throw lastError;\n }\n\n private async embedBatchOnce(\n inputCount: number,\n trimmed: ReadonlyArray<string>,\n ): Promise<EmbeddingBatchResult> {\n const response = await this.fetchImpl(`${this.baseUrl}/embeddings`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ model: this.model, input: trimmed }),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n const retryAfterHeader = response.headers.get('retry-after');\n const retryAfterMs =\n retryAfterHeader && /^\\d+$/.test(retryAfterHeader) ? Number(retryAfterHeader) * 1000 : undefined;\n throw new EmbeddingHttpError(\n `EmbeddingClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n response.status,\n retryAfterMs,\n );\n }\n const rawBody = await response.text();\n let body: OpenAIEmbeddingApiResponse;\n try {\n body = JSON.parse(rawBody) as OpenAIEmbeddingApiResponse;\n } catch {\n throw new Error(\n `EmbeddingClient: ${this.provider} returned unparseable body: ${rawBody}`,\n );\n }\n if (!Array.isArray(body.data)) {\n // OpenRouter reports provider hiccups as 200 + `{error}` with no data —\n // surface the real body instead of a blind \"missing data\" (G9: this\n // error was undiagnosable for weeks because the body was discarded).\n const detail = body.error?.message ?? rawBody;\n throw new Error(\n `EmbeddingClient: ${this.provider} response has no \\`data\\` array: ${detail}`,\n );\n }\n const ordered = [...body.data].sort((a, b) => a.index - b.index);\n if (ordered.length !== inputCount) {\n throw new Error(\n `EmbeddingClient: provider returned ${ordered.length} embeddings for ${inputCount} inputs`,\n );\n }\n return {\n embeddings: ordered.map((d) => d.embedding),\n usage: {\n promptTokens: body.usage?.prompt_tokens ?? 0,\n totalTokens: body.usage?.total_tokens ?? 0,\n },\n };\n }\n\n private resolveApiKey(): string | undefined {\n const envVar = API_KEY_ENV_VARS[this.provider];\n return envVar ? process.env[envVar] : undefined;\n }\n}\n\n/**\n * Cosine similarity between two equal-length vectors. Returns a value in\n * [-1, 1]; higher means more similar. Used by `@almadar-io/agent`'s\n * catalog retrieval to rank organism/atom descriptions against the user\n * request embedding.\n *\n * Defensive: returns 0 when either vector is the zero vector (would\n * otherwise divide by zero).\n */\nexport function cosineSimilarity(a: ReadonlyArray<number>, b: ReadonlyArray<number>): number {\n if (a.length !== b.length) {\n throw new Error(`cosineSimilarity: vector length mismatch (${a.length} vs ${b.length})`);\n }\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const av = a[i];\n const bv = b[i];\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n if (normA === 0 || normB === 0) return 0;\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n","/**\n * Image Client\n *\n * Single-purpose client for image-generation APIs. `openrouter` posts to\n * `POST {baseUrl}/images`; `local` posts an OpenAI-shaped body to\n * `POST {baseUrl}/images/generations` (the MFLUX shim in\n * tools/asset-workflow/python/image_server). Raw `fetch`, no SDK — mirrors\n * `embedding-client.ts`'s shape (provider tables, retry policy, the\n * three response-error surfaces).\n *\n * @packageDocumentation\n */\n\nimport type { JsonValue } from '@almadar/core';\nimport { Agent as UndiciAgent, fetch as undiciFetch, type RequestInit as UndiciRequestInit, type Response as UndiciResponse } from 'undici';\n\nexport type ImageProvider = 'openrouter' | 'local';\n\nconst IMAGE_PROVIDERS: readonly ImageProvider[] = ['openrouter', 'local'];\n\n/** Canonical image-provider resolution, mirrors `resolveDefaultEmbeddingProvider`. */\nexport function resolveDefaultImageProvider(): ImageProvider {\n const v = process.env.IMAGE_PROVIDER;\n return v && (IMAGE_PROVIDERS as readonly string[]).includes(v) ? (v as ImageProvider) : 'openrouter';\n}\n\nexport const OPENROUTER_IMAGE_MODELS = {\n SEEDREAM_5_LITE: 'bytedance-seed/seedream-5-0-lite',\n SEEDREAM_4_5: 'bytedance-seed/seedream-4.5',\n GEMINI_3_1_FLASH_LITE_IMAGE: 'google/gemini-3.1-flash-lite-image',\n GEMINI_2_5_FLASH_IMAGE: 'google/gemini-2.5-flash-image',\n FLUX_2_PRO: 'black-forest-labs/flux.2-pro',\n} as const;\n\nexport const OPENROUTER_IMAGE_DEFAULTS = {\n sheet: OPENROUTER_IMAGE_MODELS.SEEDREAM_5_LITE,\n preview: OPENROUTER_IMAGE_MODELS.GEMINI_3_1_FLASH_LITE_IMAGE,\n} as const;\n\nexport const LOCAL_IMAGE_MODELS = {\n Z_IMAGE_TURBO: 'z-image-turbo',\n FLUX_SCHNELL: 'flux-schnell',\n FLUX_DEV: 'flux-dev',\n FLUX_KONTEXT: 'flux-kontext',\n FLUX2_KLEIN: 'flux2-klein',\n} as const;\n\nexport const LOCAL_IMAGE_DEFAULTS = {\n /** Z-Image-Turbo is the default: public on Hugging Face, no token required (FLUX is gated). */\n sheet: process.env.LOCAL_IMAGE_SHEET_MODEL ?? LOCAL_IMAGE_MODELS.Z_IMAGE_TURBO,\n preview: process.env.LOCAL_IMAGE_PREVIEW_MODEL ?? LOCAL_IMAGE_MODELS.Z_IMAGE_TURBO,\n};\n\nexport interface ImageClientOptions {\n provider?: ImageProvider;\n model?: string;\n apiKey?: string;\n baseUrl?: string;\n fetchImpl?: typeof fetch;\n}\n\n/** An input reference image — `https:` or `data:` URL. Transport only, no fetching/validation. */\nexport interface ImageReference {\n url: string;\n}\n\nexport interface ImageGenerateOptions {\n prompt: string;\n model?: string;\n n?: number;\n size?: `${number}x${number}`;\n resolution?: '512' | '1K' | '2K' | '4K';\n aspectRatio?: string;\n quality?: 'auto' | 'low' | 'medium' | 'high';\n background?: 'auto' | 'transparent' | 'opaque';\n seed?: number;\n /** Diffusion step count (local shim; 1–100). */\n steps?: number;\n /** img2img denoise strength 0–1 (local shim `image_strength`), used with `references`. */\n strength?: number;\n references?: ReadonlyArray<ImageReference>;\n provider?: { order?: string[]; only?: string[]; allowFallbacks?: boolean };\n}\n\nexport interface GeneratedImage {\n bytes: Uint8Array;\n mediaType: string;\n}\n\nexport interface ImageGenerateResult {\n images: GeneratedImage[];\n usage: { cost: number; totalTokens: number };\n model: string;\n dropped: string[];\n}\n\nexport interface ImageModelCapabilities {\n model: string;\n supportedParameters: ReadonlySet<string>;\n raw: JsonValue;\n}\n\n/** Transient-failure retry policy: attempts and backoff (ms) between them. */\nconst IMAGE_MAX_ATTEMPTS = 3;\nconst IMAGE_RETRY_BACKOFF_MS = [500, 2000];\n\nconst DEFAULT_MODELS: Record<ImageProvider, string> = {\n openrouter: OPENROUTER_IMAGE_DEFAULTS.sheet,\n local: LOCAL_IMAGE_DEFAULTS.sheet,\n};\n\nconst DEFAULT_BASE_URLS: Record<ImageProvider, string> = {\n openrouter: 'https://openrouter.ai/api/v1',\n local: 'http://127.0.0.1:8180/v1',\n};\n\nconst API_KEY_ENV_VARS: Record<ImageProvider, string> = {\n openrouter: 'OPEN_ROUTER_API_KEY',\n local: 'LOCAL_IMAGE_API_KEY',\n};\n\n/**\n * Request field → OpenRouter `supported_parameters` name. The single\n * place `generateChecked` reads to decide what to strip — change the\n * mapping here, not at each call site.\n */\nconst IMAGE_CHECKED_PARAM_NAMES = {\n background: 'background',\n seed: 'seed',\n steps: 'steps',\n strength: 'image_strength',\n references: 'input_references',\n} as const;\n\nfunction isJsonObject(value: JsonValue): value is { [key: string]: JsonValue } {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: JsonValue | undefined): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asNumber(value: JsonValue | undefined): number | undefined {\n return typeof value === 'number' ? value : undefined;\n}\n\nfunction parseJsonBody(provider: ImageProvider, rawBody: string): JsonValue {\n try {\n return JSON.parse(rawBody) as JsonValue;\n } catch {\n throw new Error(`ImageClient: ${provider} returned unparseable body: ${rawBody}`);\n }\n}\n\nfunction decodeBase64(b64: string): Uint8Array {\n if (typeof globalThis.Buffer !== 'undefined') {\n return new Uint8Array(globalThis.Buffer.from(b64, 'base64'));\n }\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n/**\n * Collects the union of `supported_parameters` across a `.../endpoints`\n * response's `data`/`endpoints` arrays. Returns an empty set (never\n * throws) when the shape doesn't match — the caller still gets `raw`.\n */\nfunction collectSupportedParameters(body: JsonValue): ReadonlySet<string> {\n const params = new Set<string>();\n if (!isJsonObject(body)) return params;\n const candidateArrays: JsonValue[][] = [];\n if (Array.isArray(body.data)) candidateArrays.push(body.data);\n if (Array.isArray(body.endpoints)) candidateArrays.push(body.endpoints);\n for (const arr of candidateArrays) {\n for (const entry of arr) {\n if (!isJsonObject(entry)) continue;\n const sp = entry.supported_parameters;\n if (!Array.isArray(sp)) continue;\n for (const p of sp) if (typeof p === 'string') params.add(p);\n }\n }\n return params;\n}\n\nexport class ImageClient {\n private readonly provider: ImageProvider;\n private readonly model: string;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly capabilitiesCache = new Map<string, ImageModelCapabilities>();\n\n constructor(opts: ImageClientOptions = {}) {\n this.provider = opts.provider ?? resolveDefaultImageProvider();\n this.model = opts.model ?? DEFAULT_MODELS[this.provider];\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URLS[this.provider]).replace(/\\/$/, '');\n this.fetchImpl = opts.fetchImpl ?? fetch;\n\n // The local shim is unauthenticated; a key is required only for hosted providers.\n const apiKey = opts.apiKey ?? process.env[API_KEY_ENV_VARS[this.provider]] ?? '';\n if (!apiKey && this.provider !== 'local') {\n throw new Error(\n `ImageClient: API key for provider '${this.provider}' not found. ` +\n `Set ${API_KEY_ENV_VARS[this.provider]} in your environment or pass options.apiKey.`,\n );\n }\n this.apiKey = apiKey;\n }\n\n /** Generate images. Never strips request fields — `dropped` is always `[]`. */\n async generate(opts: ImageGenerateOptions): Promise<ImageGenerateResult> {\n return this.performGenerate(opts, []);\n }\n\n /**\n * Per-model `.../endpoints` capabilities, memoized per instance per\n * model. `supportedParameters` is the empty set (not a throw) when the\n * response shape doesn't match what's expected.\n */\n async capabilities(modelId?: string): Promise<ImageModelCapabilities> {\n const model = modelId ?? this.model;\n const cached = this.capabilitiesCache.get(model);\n if (cached) return cached;\n\n // The local shim serves no `.../endpoints` metadata; declare what it honors.\n if (this.provider === 'local') {\n const local: ImageModelCapabilities = {\n model,\n supportedParameters: new Set(['seed', IMAGE_CHECKED_PARAM_NAMES.steps, IMAGE_CHECKED_PARAM_NAMES.strength, IMAGE_CHECKED_PARAM_NAMES.references]),\n raw: {},\n };\n this.capabilitiesCache.set(model, local);\n return local;\n }\n\n const response = await this.fetchWithRetry(`${this.baseUrl}/images/models/${model}/endpoints`, {\n method: 'GET',\n headers: this.authHeaders(),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n throw new Error(\n `ImageClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n );\n }\n const body = parseJsonBody(this.provider, await response.text());\n const result: ImageModelCapabilities = { model, supportedParameters: collectSupportedParameters(body), raw: body };\n this.capabilitiesCache.set(model, result);\n return result;\n }\n\n /**\n * Like `generate`, but first reads `capabilities(model)` and strips\n * any of `background`/`seed`/`references` the model doesn't list in\n * `supported_parameters` — their (OpenRouter) names come back in\n * `dropped` rather than being silently swallowed.\n */\n async generateChecked(opts: ImageGenerateOptions): Promise<ImageGenerateResult> {\n const model = opts.model ?? this.model;\n const caps = await this.capabilities(model);\n const dropped: string[] = [];\n const filtered: ImageGenerateOptions = { ...opts, model };\n\n if (filtered.background !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.background)) {\n delete filtered.background;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.background);\n }\n if (filtered.seed !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.seed)) {\n delete filtered.seed;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.seed);\n }\n if (filtered.steps !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.steps)) {\n delete filtered.steps;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.steps);\n }\n if (filtered.strength !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.strength)) {\n delete filtered.strength;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.strength);\n }\n if (\n filtered.references !== undefined &&\n filtered.references.length > 0 &&\n !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.references)\n ) {\n delete filtered.references;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.references);\n }\n\n return this.performGenerate(filtered, dropped);\n }\n\n private async performGenerate(opts: ImageGenerateOptions, dropped: string[]): Promise<ImageGenerateResult> {\n if (opts.size !== undefined && (opts.resolution !== undefined || opts.aspectRatio !== undefined)) {\n throw new Error('ImageClient.generate: `size` cannot be combined with `resolution`/`aspectRatio` — pass one or the other');\n }\n const model = opts.model ?? this.model;\n\n if (this.provider === 'local') {\n return this.performLocalGenerate(opts, model, dropped);\n }\n\n const requestBody: Record<string, JsonValue> = { model, prompt: opts.prompt, output_format: 'png' };\n if (opts.n !== undefined) requestBody.n = opts.n;\n if (opts.size !== undefined) requestBody.size = opts.size;\n if (opts.resolution !== undefined) requestBody.resolution = opts.resolution;\n if (opts.aspectRatio !== undefined) requestBody.aspect_ratio = opts.aspectRatio;\n if (opts.quality !== undefined) requestBody.quality = opts.quality;\n if (opts.background !== undefined) requestBody.background = opts.background;\n if (opts.seed !== undefined) requestBody.seed = opts.seed;\n if (opts.steps !== undefined) requestBody.steps = opts.steps;\n if (opts.references !== undefined && opts.references.length > 0) {\n requestBody.input_references = opts.references.map((r) => ({ type: 'image_url', image_url: { url: r.url } }));\n }\n if (opts.provider !== undefined) {\n const providerField: Record<string, JsonValue> = {};\n if (opts.provider.order !== undefined) providerField.order = opts.provider.order;\n if (opts.provider.only !== undefined) providerField.only = opts.provider.only;\n if (opts.provider.allowFallbacks !== undefined) providerField.allow_fallbacks = opts.provider.allowFallbacks;\n requestBody.provider = providerField;\n }\n\n return this.sendGenerate(`${this.baseUrl}/images`, requestBody, model, dropped);\n }\n\n /**\n * OpenAI-compatible local server (the MFLUX shim in tools/asset-workflow).\n * OpenRouter-only fields are reported in `dropped`, never sent. References\n * ARE sent (as `input_references`) — the shim forwards them to mflux's\n * `image_path`/`image_strength` img2img conditioning.\n */\n private async performLocalGenerate(\n opts: ImageGenerateOptions,\n model: string,\n dropped: string[],\n ): Promise<ImageGenerateResult> {\n const requestBody: Record<string, JsonValue> = { model, prompt: opts.prompt };\n if (opts.n !== undefined) requestBody.n = opts.n;\n if (opts.size !== undefined) requestBody.size = opts.size;\n if (opts.seed !== undefined) requestBody.seed = opts.seed;\n if (opts.steps !== undefined) requestBody.steps = opts.steps;\n if (opts.strength !== undefined) requestBody.image_strength = opts.strength;\n if (opts.references !== undefined && opts.references.length > 0) {\n requestBody.input_references = opts.references.map((r) => ({ type: 'image_url', image_url: { url: r.url } }));\n }\n for (const field of ['resolution', 'aspectRatio', 'quality', 'background'] as const) {\n if (opts[field] !== undefined) dropped.push(field);\n }\n if (opts.provider !== undefined) dropped.push('provider');\n\n return this.sendGenerate(`${this.baseUrl}/images/generations`, requestBody, model, dropped);\n }\n\n private authHeaders(): Record<string, string> {\n return this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {};\n }\n\n private async sendGenerate(\n url: string,\n requestBody: Record<string, JsonValue>,\n model: string,\n dropped: string[],\n ): Promise<ImageGenerateResult> {\n const response = await this.fetchWithRetry(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...this.authHeaders() },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n throw new Error(\n `ImageClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n );\n }\n const rawBody = await response.text();\n const body = parseJsonBody(this.provider, rawBody);\n if (!isJsonObject(body)) {\n throw new Error(`ImageClient: ${this.provider} returned unparseable body: ${rawBody}`);\n }\n if (isJsonObject(body.error)) {\n const detail = asString(body.error.message) ?? rawBody;\n throw new Error(`ImageClient: ${this.provider} returned an error: ${detail}`);\n }\n if (!Array.isArray(body.data)) {\n throw new Error(`ImageClient: ${this.provider} response has no \\`data\\` array: ${rawBody}`);\n }\n\n const images: GeneratedImage[] = body.data.map((entry, i) => {\n if (!isJsonObject(entry)) {\n throw new Error(`ImageClient: ${this.provider} response data[${i}] is not an object: ${rawBody}`);\n }\n const b64 = asString(entry.b64_json);\n if (!b64) {\n throw new Error(`ImageClient: ${this.provider} response data[${i}] missing \\`b64_json\\`: ${rawBody}`);\n }\n return { bytes: decodeBase64(b64), mediaType: asString(entry.media_type) ?? 'image/png' };\n });\n\n const usageObj = isJsonObject(body.usage) ? body.usage : undefined;\n const usage = {\n cost: asNumber(usageObj?.cost) ?? 0,\n totalTokens: asNumber(usageObj?.total_tokens) ?? 0,\n };\n\n return { images, usage, model, dropped };\n }\n\n /** Retries on network failure or a 5xx status; any other response is returned as-is.\n * The local provider never retries: a failed local generation (e.g. MLX OOM) cannot\n * succeed on an immediate retry, and hammering a memory-pressured machine is harmful. */\n private async fetchWithRetry(url: string, init: RequestInit): Promise<Response | UndiciResponse> {\n const maxAttempts = this.provider === 'local' ? 1 : IMAGE_MAX_ATTEMPTS;\n let lastNetworkError: Error | undefined;\n let response: Response | UndiciResponse | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n response = await this.doFetch(url, init);\n } catch (e) {\n response = undefined;\n lastNetworkError = e instanceof Error ? e : new Error(String(e));\n }\n if (response !== undefined && response.status < 500) return response;\n const backoff = IMAGE_RETRY_BACKOFF_MS[attempt - 1];\n if (backoff === undefined || attempt >= maxAttempts) break;\n await new Promise((r) => setTimeout(r, backoff));\n }\n if (response) return response;\n throw lastNetworkError ?? new Error(`ImageClient: ${this.provider} request failed after ${maxAttempts} attempts`);\n }\n\n /** Local generations can exceed Node's built-in 300s headersTimeout (a single\n * MFLUX image routinely takes minutes), so local calls ride npm-undici's own\n * fetch with the transport timers disabled — mirrors `LLMClient.localDispatcher`. */\n private static localDispatcher: UndiciAgent | null = null;\n\n private static getLocalDispatcher(): UndiciAgent {\n if (ImageClient.localDispatcher === null) {\n ImageClient.localDispatcher = new UndiciAgent({ headersTimeout: 0, bodyTimeout: 0 });\n }\n return ImageClient.localDispatcher;\n }\n\n private async doFetch(url: string, init: RequestInit): Promise<Response | UndiciResponse> {\n if (this.provider === 'local' && this.fetchImpl === fetch) {\n const uinit: UndiciRequestInit = {\n method: init.method,\n headers: init.headers as UndiciRequestInit['headers'],\n body: init.body as UndiciRequestInit['body'],\n dispatcher: ImageClient.getLocalDispatcher(),\n };\n return undiciFetch(url, uinit);\n }\n return this.fetchImpl(url, init);\n }\n}\n\nexport const IMAGE_PROVIDER_CATALOG: ReadonlyArray<{ provider: ImageProvider; models: string[]; defaultModel: string }> = [\n {\n provider: 'openrouter',\n models: Object.values(OPENROUTER_IMAGE_MODELS),\n defaultModel: OPENROUTER_IMAGE_DEFAULTS.sheet,\n },\n {\n provider: 'local',\n models: Object.values(LOCAL_IMAGE_MODELS),\n defaultModel: LOCAL_IMAGE_DEFAULTS.sheet,\n },\n];\n","/**\n * Truncation Detector\n *\n * Utilities for detecting when LLM output has been truncated and\n * extracting usable content from partial responses.\n *\n * @packageDocumentation\n */\n\nimport type { LLMFinishReason } from './client.js';\nimport { autoCloseJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type TruncationReason =\n | 'finish_reason'\n | 'json_incomplete'\n | 'bracket_mismatch'\n | 'none';\n\nexport interface TruncationResult {\n isTruncated: boolean;\n reason: TruncationReason;\n partialContent?: string;\n lastCompleteElement?: unknown;\n missingCloseBrackets?: number;\n missingCloseBraces?: number;\n}\n\n// ============================================================================\n// Main Detection Function\n// ============================================================================\n\n/**\n * Detect if an LLM response has been truncated.\n *\n * Analyzes the response content and finish reason to determine if\n * the output was cut off due to token limits or other issues.\n *\n * @param {string} response - The LLM response text\n * @param {LLMFinishReason} finishReason - The finish reason from the LLM\n * @returns {TruncationResult} Detection result with truncation details\n */\nexport function detectTruncation(\n response: string,\n finishReason: LLMFinishReason,\n): TruncationResult {\n if (finishReason === 'length') {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'finish_reason',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n JSON.parse(response);\n return { isTruncated: false, reason: 'none' };\n } catch {\n // JSON is invalid, check if due to truncation\n }\n\n if (finishReason === 'stop' || finishReason === null) {\n const trimmed = response.trim();\n\n const isMidContent =\n trimmed.endsWith(',') ||\n trimmed.endsWith(':') ||\n trimmed.endsWith('\": ') ||\n /:\\s*$/.test(trimmed) ||\n /,\\s*$/.test(trimmed);\n\n if (isMidContent) {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'json_incomplete',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n const closed = autoCloseJson(trimmed);\n JSON.parse(closed);\n return { isTruncated: false, reason: 'none' };\n } catch {\n return { isTruncated: false, reason: 'none' };\n }\n }\n\n const bracketInfo = countBrackets(response);\n if (\n bracketInfo.missingCloseBrackets > 0 ||\n bracketInfo.missingCloseBraces > 0\n ) {\n return {\n isTruncated: true,\n reason: 'bracket_mismatch',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n return { isTruncated: false, reason: 'none' };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction countBrackets(json: string): {\n openBrackets: number;\n closeBrackets: number;\n openBraces: number;\n closeBraces: number;\n missingCloseBrackets: number;\n missingCloseBraces: number;\n} {\n let inString = false;\n let escaped = false;\n let openBrackets = 0;\n let closeBrackets = 0;\n let openBraces = 0;\n let closeBraces = 0;\n\n for (const char of json) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n switch (char) {\n case '[':\n openBrackets++;\n break;\n case ']':\n closeBrackets++;\n break;\n case '{':\n openBraces++;\n break;\n case '}':\n closeBraces++;\n break;\n }\n }\n\n return {\n openBrackets,\n closeBrackets,\n openBraces,\n closeBraces,\n missingCloseBrackets: Math.max(0, openBrackets - closeBrackets),\n missingCloseBraces: Math.max(0, openBraces - closeBraces),\n };\n}\n\nexport function findLastCompleteElement(json: string): unknown | null {\n const autoClosed = autoCloseJson(json);\n try {\n return JSON.parse(autoClosed);\n } catch {\n // Auto-close didn't work\n }\n\n const trimmed = json.trim();\n\n if (trimmed.startsWith('[')) {\n const lastCompleteIndex = findLastCompleteArrayElement(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + ']';\n try {\n return JSON.parse(subset);\n } catch {\n // Continue\n }\n }\n }\n\n if (trimmed.startsWith('{')) {\n const closed = autoCloseJson(trimmed);\n try {\n return JSON.parse(closed);\n } catch {\n const lastCompleteIndex = findLastCompleteObjectProperty(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + '}';\n try {\n return JSON.parse(subset);\n } catch {\n // Give up\n }\n }\n }\n }\n\n return null;\n}\n\nfunction findLastCompleteArrayElement(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCompleteElementEnd = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n if (depth === 1) {\n lastCompleteElementEnd = i + 1;\n }\n } else if (char === ',' && depth === 1) {\n lastCompleteElementEnd = i;\n }\n }\n\n return lastCompleteElementEnd > 0 ? lastCompleteElementEnd : -1;\n}\n\nfunction findLastCompleteObjectProperty(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCommaIndex = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n } else if (char === ',' && depth === 1) {\n lastCommaIndex = i;\n }\n }\n\n return lastCommaIndex > 0 ? lastCommaIndex : -1;\n}\n\nexport function isLikelyTruncated(content: string): boolean {\n const trimmed = content.trim();\n if (!trimmed) return false;\n\n const brackets = countBrackets(trimmed);\n if (\n brackets.missingCloseBrackets > 0 ||\n brackets.missingCloseBraces > 0\n ) {\n return true;\n }\n\n const abruptEndings = [\n /,\\s*$/,\n /:\\s*$/,\n /\"\\s*:\\s*$/,\n /\\[\\s*$/,\n /{\\s*$/,\n ];\n\n for (const pattern of abruptEndings) {\n if (pattern.test(trimmed)) return true;\n }\n\n return false;\n}\n","/**\n * LLM Continuation Utility\n *\n * Handles truncated LLM responses with automatic continuation.\n * - Detects truncation via finish_reason and JSON structure\n * - Automatically continues with full context\n * - Merges partial and continuation responses\n * - Salvages partial data if max continuations reached\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod';\nimport { LLMClient, type LLMFinishReason } from './client.js';\nimport { detectTruncation } from './truncation-detector.js';\nimport { extractJsonFromText, autoCloseJson, isValidJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ContinuationOptions<T> {\n client: LLMClient;\n systemPrompt: string;\n userPrompt: string;\n schema?: z.ZodSchema<T>;\n maxTokens?: number;\n maxContinuations?: number;\n maxRetries?: number;\n buildContinuationPrompt: (\n partialResponse: string,\n attempt: number,\n ) => string;\n continuationSystemPrompt?: string;\n}\n\nexport interface ContinuationResult<T> {\n data: T;\n raw: string;\n continuationCount: number;\n warnings: string[];\n wasSalvaged: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MAX_TOKENS = 8192;\nconst DEFAULT_MAX_CONTINUATIONS = 3;\n\n/**\n * Default continuation system prompt.\n * Used when no custom continuationSystemPrompt is provided.\n */\nconst DEFAULT_CONTINUATION_SYSTEM_PROMPT = `You are a JSON continuation assistant. Your ONLY job is to continue generating JSON from where the previous response was truncated.\n\nRules:\n1. Continue from EXACTLY where the previous output stopped\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly with all closing brackets\n4. Do NOT wrap in markdown code blocks\n5. Output ONLY the continuation JSON, nothing else`;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Merge a previous partial response with a continuation.\n *\n * Handles JSON structure merging, removing markdown code blocks,\n * and ensuring proper comma separation between merged parts.\n *\n * @param {string} previous - The previous partial response\n * @param {string} continuation - The continuation to merge\n * @returns {string} The merged response\n */\nexport function mergeResponses(\n previous: string,\n continuation: string,\n): string {\n const trimmedPrev = previous.trimEnd();\n const trimmedCont = continuation.trimStart();\n\n let cleanedCont = trimmedCont\n .replace(/^```json?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\n if (cleanedCont.startsWith('{')) {\n try {\n const contParsed = JSON.parse(autoCloseJson(cleanedCont));\n const keys = Object.keys(contParsed);\n if (keys.length === 1 && Array.isArray(contParsed[keys[0]])) {\n cleanedCont = contParsed[keys[0]]\n .map((item: unknown) => JSON.stringify(item))\n .join(',\\n');\n }\n } catch {\n // Continue with original cleaning\n }\n }\n\n if (cleanedCont.startsWith('}') || cleanedCont.startsWith(']')) {\n return trimmedPrev + cleanedCont;\n }\n\n const prevEndsWithValue = /[\\}\\]\\\"\\d]$/.test(trimmedPrev);\n const contStartsWithValue = /^[\\{\\[\\\"]/.test(cleanedCont);\n\n if (prevEndsWithValue && contStartsWithValue) {\n return trimmedPrev + ',\\n' + cleanedCont;\n }\n\n return trimmedPrev + cleanedCont;\n}\n\nexport function salvagePartialResponse<T>(rawResponse: string): T | null {\n console.warn('[Continuation] Attempting to salvage partial response');\n\n try {\n const cleanedResponse = extractJsonFromText(rawResponse) || rawResponse;\n const closed = autoCloseJson(cleanedResponse);\n const parsed = JSON.parse(closed) as T;\n console.log('[Continuation] Successfully salvaged partial response');\n return parsed;\n } catch (error) {\n console.error('[Continuation] Could not salvage response:', error);\n }\n\n return null;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Call an LLM with automatic continuation handling.\n *\n * Manages token limits by detecting truncation and making continuation\n * calls until the complete response is received or max continuations reached.\n *\n * @template T - Expected response type\n * @param {ContinuationOptions<T>} options - Continuation call options\n * @returns {Promise<ContinuationResult<T>>} Result with data and continuation metadata\n */\nexport async function callWithContinuation<T>(\n options: ContinuationOptions<T>,\n): Promise<ContinuationResult<T>> {\n const {\n client,\n systemPrompt,\n userPrompt,\n schema,\n maxTokens = DEFAULT_MAX_TOKENS,\n maxContinuations = DEFAULT_MAX_CONTINUATIONS,\n buildContinuationPrompt,\n continuationSystemPrompt = DEFAULT_CONTINUATION_SYSTEM_PROMPT,\n } = options;\n\n let rawResponse = '';\n let continuationCount = 0;\n const warnings: string[] = [];\n let wasSalvaged = false;\n\n console.log('[Continuation] Starting LLM call with continuation support');\n console.log(\n `[Continuation] Max tokens: ${maxTokens}, Max continuations: ${maxContinuations}`,\n );\n\n try {\n const response = await client.callRawWithMetadata({\n systemPrompt,\n userPrompt,\n maxTokens,\n });\n\n rawResponse = extractJsonFromText(response.raw) || response.raw;\n\n console.log(\n `[Continuation] Initial response: ${rawResponse.length} chars, finish_reason: ${response.finishReason}`,\n );\n\n let truncation = detectTruncation(rawResponse, response.finishReason);\n\n while (truncation.isTruncated && continuationCount < maxContinuations) {\n continuationCount++;\n const warningMsg = `Response truncated (${truncation.reason}), continuing (attempt ${continuationCount}/${maxContinuations})`;\n console.log(`[Continuation] ${warningMsg}`);\n warnings.push(warningMsg);\n\n const contPrompt = buildContinuationPrompt(\n rawResponse,\n continuationCount,\n );\n\n const contResponse = await client.callRawWithMetadata({\n systemPrompt: continuationSystemPrompt,\n userPrompt: contPrompt,\n maxTokens,\n });\n\n console.log(\n `[Continuation] Continuation response: ${contResponse.raw.length} chars, finish_reason: ${contResponse.finishReason}`,\n );\n\n const cleanedContResponse =\n extractJsonFromText(contResponse.raw) || contResponse.raw;\n rawResponse = mergeResponses(rawResponse, cleanedContResponse);\n\n truncation = detectTruncation(rawResponse, contResponse.finishReason);\n }\n\n if (\n continuationCount >= maxContinuations &&\n truncation.isTruncated\n ) {\n console.warn(\n `[Continuation] Reached max continuations (${maxContinuations}), attempting to salvage...`,\n );\n warnings.push(\n `Reached max continuations - some content may be incomplete`,\n );\n wasSalvaged = true;\n }\n\n const cleanedResponse =\n extractJsonFromText(rawResponse) || rawResponse;\n let data: T;\n\n try {\n if (isValidJson(cleanedResponse)) {\n data = JSON.parse(cleanedResponse) as T;\n } else {\n const closed = autoCloseJson(cleanedResponse);\n data = JSON.parse(closed) as T;\n if (!wasSalvaged) {\n warnings.push('Response required auto-closing of JSON brackets');\n }\n }\n } catch (parseError) {\n const salvaged = salvagePartialResponse<T>(cleanedResponse);\n if (salvaged) {\n data = salvaged;\n wasSalvaged = true;\n warnings.push('Response was salvaged from partial data');\n } else {\n throw new Error(\n `Failed to parse response after ${continuationCount} continuations: ${parseError}`,\n );\n }\n }\n\n if (schema) {\n try {\n data = schema.parse(data);\n } catch (validationError) {\n console.warn(\n '[Continuation] Schema validation failed:',\n validationError,\n );\n warnings.push(`Schema validation issue: ${validationError}`);\n }\n }\n\n console.log(\n `[Continuation] Complete. Continuations: ${continuationCount}, Warnings: ${warnings.length}`,\n );\n\n return {\n data,\n raw: rawResponse,\n continuationCount,\n warnings,\n wasSalvaged,\n };\n } catch (error) {\n console.error('[Continuation] Error during LLM call:', error);\n throw error;\n }\n}\n\n/**\n * Build a generic continuation prompt.\n *\n * Creates a prompt that instructs the LLM to continue from where\n * a previous truncated response left off.\n *\n * @param {string} context - Original context/prompt\n * @param {string} partialResponse - The partial response generated so far\n * @param {number} attempt - Current continuation attempt number\n * @param {number} [maxAttempts] - Maximum allowed continuation attempts\n * @returns {string} The formatted continuation prompt\n */\nexport function buildGenericContinuationPrompt(\n context: string,\n partialResponse: string,\n attempt: number,\n maxAttempts: number = DEFAULT_MAX_CONTINUATIONS,\n): string {\n return `## CONTINUATION REQUEST (Attempt ${attempt}/${maxAttempts})\n\nYour previous response was truncated. Continue generating from where you left off.\n\n### ORIGINAL CONTEXT\n${context}\n\n### WHAT YOU GENERATED SO FAR\n\\`\\`\\`json\n${partialResponse}\n\\`\\`\\`\n\n### INSTRUCTIONS\n1. Continue from EXACTLY where the response was cut off\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly\n4. Do NOT wrap your response in markdown code blocks\n\nContinue generating now:`;\n}\n"],"mappings":"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","names":["DEFAULT_MODELS","DEFAULT_BASE_URLS","API_KEY_ENV_VARS","bracketInfo"]}
|
|
1
|
+
{"version":3,"sources":["../src/embedding-client.ts","../src/image-client.ts","../src/truncation-detector.ts","../src/continuation.ts","../src/scripted-tool-client.ts"],"sourcesContent":["/**\n * Embedding Client\n *\n * Single-purpose client for text embeddings. Used by `@almadar-io/agent`'s\n * cosine-similarity catalog retrieval (rank organisms + atoms against the\n * user's request before rendering Stage A's prompt) and by\n * `@almadar/std`'s publish-time embedding bake step.\n *\n * Providers:\n * - `openai` (default model `text-embedding-3-small`, 1536-d) — requires\n * `OPENAI_API_KEY`.\n * - `openrouter` (default model `baai/bge-base-en-v1.5`, 768-d) — requires\n * `OPEN_ROUTER_API_KEY`. Same OpenAI-compatible request shape, just a\n * different base URL.\n * - `lmstudio` (default model `text-embedding-bge-base-en-v1.5`, same\n * bge-base-en-v1.5 weights as the openrouter default, run locally) —\n * local LM Studio server, no API key. Mirrors `client.ts`'s\n * `PROVIDER_CONFIGS.lmstudio`: base URL from `LMSTUDIO_URL` (default\n * `http://127.0.0.1:1234/v1`), model from `LMSTUDIO_EMBEDDING_MODEL`.\n *\n * All three providers return the same response envelope (`{data:[{embedding,index}]}`),\n * so the request/response code is shared.\n *\n * @packageDocumentation\n */\n\nexport type EmbeddingProvider = 'openai' | 'openrouter' | 'lmstudio';\n\nconst EMBEDDING_PROVIDERS: readonly EmbeddingProvider[] = ['openai', 'openrouter', 'lmstudio'];\n\n/**\n * Canonical embedding-provider resolution — the ONE place every downstream\n * consumer should defer to instead of hardcoding a literal provider.\n * Mirrors the `EMBEDDING_PROVIDER` env var `@almadar-io/integrations`'s\n * `RuntimeIntegrationManager`/`getEmbeddings()` already reads (its own\n * `?? 'openrouter'` fallback is the precedent this centralizes).\n *\n * Defaults to `openrouter` — UNCHANGED from every existing deployment's\n * behavior — so nothing (cloud/server included) switches to a local-only\n * provider unless an operator explicitly sets `EMBEDDING_PROVIDER=lmstudio`\n * in that specific environment (e.g. a dev machine running LM Studio).\n * Consumers: `@almadar-io/rabit`'s catalog narrower\n * (`runtime/services/embedding.ts`) and `@almadar/workspace`'s index\n * embedder (`workspace-index/embedder.ts`).\n */\nexport function resolveDefaultEmbeddingProvider(): EmbeddingProvider {\n const v = process.env.EMBEDDING_PROVIDER;\n return v && (EMBEDDING_PROVIDERS as readonly string[]).includes(v) ? (v as EmbeddingProvider) : 'openrouter';\n}\n\nexport interface EmbeddingClientOptions {\n provider: EmbeddingProvider;\n /** Defaults: openai → text-embedding-3-small, openrouter → baai/bge-base-en-v1.5, lmstudio → text-embedding-bge-base-en-v1.5. */\n model?: string;\n /** Override API key. Defaults to provider-specific env var; lmstudio needs none. */\n apiKey?: string;\n /** Override base URL. Defaults to provider canonical endpoint (lmstudio: `LMSTUDIO_URL`). */\n baseUrl?: string;\n /** Override the fetch implementation (for tests). Defaults to global `fetch`. */\n fetchImpl?: typeof fetch;\n}\n\nexport interface EmbeddingUsage {\n promptTokens: number;\n totalTokens: number;\n}\n\nexport interface EmbeddingResult {\n embedding: readonly number[];\n usage: EmbeddingUsage;\n}\n\nexport interface EmbeddingBatchResult {\n embeddings: readonly (readonly number[])[];\n usage: EmbeddingUsage;\n}\n\ninterface OpenAIEmbeddingApiResponse {\n data: Array<{ embedding: number[]; index: number }>;\n usage?: { prompt_tokens?: number; total_tokens?: number };\n /** OpenRouter reports provider hiccups as HTTP 200 with an error envelope and no `data`. */\n error?: { message?: string; code?: number | string };\n}\n\n/**\n * Thrown by `embedBatchOnce` on a non-OK HTTP response. Carries the status\n * code (and any `Retry-After` hint) as structured fields so the retry loop\n * can react to a specific status — a 429 in particular — without\n * string-matching the error message.\n */\nclass EmbeddingHttpError extends Error {\n constructor(\n message: string,\n readonly status: number,\n readonly retryAfterMs?: number,\n ) {\n super(message);\n this.name = 'EmbeddingHttpError';\n }\n}\n\n/** Transient-failure retry policy: attempts and backoff (ms) between them. */\nconst EMBED_MAX_ATTEMPTS = 3;\nconst EMBED_RETRY_BACKOFF_MS = [500, 2000];\n\n/**\n * HTTP 429 gets its own, longer retry budget. A rate limit (\"model busy,\n * retry later\") is the single most retryable failure there is, but it\n * rarely clears within the generic transient-failure window above — the\n * provider is telling you to wait, sometimes via `Retry-After`, which this\n * honors when present instead of guessing.\n */\nconst EMBED_RATE_LIMIT_MAX_ATTEMPTS = 4;\nconst EMBED_RATE_LIMIT_BACKOFF_MS = [2000, 5000, 15000];\n\n/** Provider rejects an input that exceeds the model's context window (bge: 512 tokens). */\nconst TOKEN_OVERFLOW_RE =\n /context length|maximum input length|reduce the length of the input|input_tokens/i;\n\nconst DEFAULT_MODELS: Record<EmbeddingProvider, string> = {\n openai: 'text-embedding-3-small',\n openrouter: 'baai/bge-base-en-v1.5',\n lmstudio: 'text-embedding-bge-base-en-v1.5',\n};\n\nconst DEFAULT_BASE_URLS: Record<EmbeddingProvider, string> = {\n openai: 'https://api.openai.com/v1',\n openrouter: 'https://openrouter.ai/api/v1',\n lmstudio: 'http://127.0.0.1:1234/v1',\n};\n\nconst API_KEY_ENV_VARS: Partial<Record<EmbeddingProvider, string>> = {\n openai: 'OPENAI_API_KEY',\n openrouter: 'OPEN_ROUTER_API_KEY',\n};\n\nexport class EmbeddingClient {\n private readonly provider: EmbeddingProvider;\n private readonly model: string;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n\n constructor(opts: EmbeddingClientOptions) {\n this.provider = opts.provider;\n this.model = opts.model ?? (this.provider === 'lmstudio' ? process.env.LMSTUDIO_EMBEDDING_MODEL ?? DEFAULT_MODELS.lmstudio : DEFAULT_MODELS[this.provider]);\n const defaultBaseUrl = this.provider === 'lmstudio' ? process.env.LMSTUDIO_URL ?? DEFAULT_BASE_URLS.lmstudio : DEFAULT_BASE_URLS[this.provider];\n this.baseUrl = (opts.baseUrl ?? defaultBaseUrl).replace(/\\/$/, '');\n this.fetchImpl = opts.fetchImpl ?? fetch;\n\n if (this.provider === 'lmstudio') {\n // Local LM Studio server — no credential to check, mirrors client.ts's PROVIDER_CONFIGS.lmstudio.\n this.apiKey = opts.apiKey ?? 'not-needed';\n return;\n }\n const apiKey = opts.apiKey ?? this.resolveApiKey();\n if (!apiKey) {\n throw new Error(\n `EmbeddingClient: API key for provider '${this.provider}' not found. ` +\n `Set ${API_KEY_ENV_VARS[this.provider]} in your environment or pass options.apiKey.`,\n );\n }\n this.apiKey = apiKey;\n }\n\n /** Embed a single text. */\n async embed(text: string): Promise<EmbeddingResult> {\n const batch = await this.embedBatch([text]);\n const embedding = batch.embeddings[0];\n if (!embedding) {\n throw new Error('EmbeddingClient.embed: provider returned no embedding');\n }\n return { embedding, usage: batch.usage };\n }\n\n /**\n * Embed a batch of texts. Used by `@almadar/std`'s publish-time\n * `build-embeddings.ts` script — one batch call for the full catalog\n * (≈190 entries) rather than per-entry requests.\n */\n async embedBatch(texts: ReadonlyArray<string>): Promise<EmbeddingBatchResult> {\n if (texts.length === 0) {\n return { embeddings: [], usage: { promptTokens: 0, totalTokens: 0 } };\n }\n // bge-base-en-v1.5 has a 512-token context limit. Chars-per-token varies\n // with density (~4 for typical English, ~2.5-3 for dense technical/code\n // text), so no fixed char cap can guarantee a fit. Start at 2000 chars and,\n // when the provider rejects an input for exceeding the context window,\n // halve the cap and retry — the provider's own token count is the source\n // of truth, not a chars-per-token guess.\n let charCap = 2000;\n let lastError: Error = new Error('EmbeddingClient: unreachable');\n let genericAttempt = 0;\n let rateLimitAttempt = 0;\n for (;;) {\n const trimmed = texts.map((t) => (t.length > charCap ? t.slice(0, charCap) : t));\n try {\n return await this.embedBatchOnce(texts.length, trimmed);\n } catch (e) {\n lastError = e instanceof Error ? e : new Error(String(e));\n if (TOKEN_OVERFLOW_RE.test(lastError.message) && charCap > 500) {\n charCap = Math.floor(charCap / 2);\n continue;\n }\n if (lastError instanceof EmbeddingHttpError && lastError.status === 429) {\n if (rateLimitAttempt >= EMBED_RATE_LIMIT_MAX_ATTEMPTS - 1) break;\n const wait = lastError.retryAfterMs ?? EMBED_RATE_LIMIT_BACKOFF_MS[rateLimitAttempt];\n rateLimitAttempt++;\n await new Promise((r) => setTimeout(r, wait));\n continue;\n }\n genericAttempt++;\n const backoff = EMBED_RETRY_BACKOFF_MS[genericAttempt - 1];\n if (genericAttempt >= EMBED_MAX_ATTEMPTS || backoff === undefined) break;\n await new Promise((r) => setTimeout(r, backoff));\n }\n }\n throw lastError;\n }\n\n private async embedBatchOnce(\n inputCount: number,\n trimmed: ReadonlyArray<string>,\n ): Promise<EmbeddingBatchResult> {\n const response = await this.fetchImpl(`${this.baseUrl}/embeddings`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${this.apiKey}`,\n },\n body: JSON.stringify({ model: this.model, input: trimmed }),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n const retryAfterHeader = response.headers.get('retry-after');\n const retryAfterMs =\n retryAfterHeader && /^\\d+$/.test(retryAfterHeader) ? Number(retryAfterHeader) * 1000 : undefined;\n throw new EmbeddingHttpError(\n `EmbeddingClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n response.status,\n retryAfterMs,\n );\n }\n const rawBody = await response.text();\n let body: OpenAIEmbeddingApiResponse;\n try {\n body = JSON.parse(rawBody) as OpenAIEmbeddingApiResponse;\n } catch {\n throw new Error(\n `EmbeddingClient: ${this.provider} returned unparseable body: ${rawBody}`,\n );\n }\n if (!Array.isArray(body.data)) {\n // OpenRouter reports provider hiccups as 200 + `{error}` with no data —\n // surface the real body instead of a blind \"missing data\" (G9: this\n // error was undiagnosable for weeks because the body was discarded).\n const detail = body.error?.message ?? rawBody;\n throw new Error(\n `EmbeddingClient: ${this.provider} response has no \\`data\\` array: ${detail}`,\n );\n }\n const ordered = [...body.data].sort((a, b) => a.index - b.index);\n if (ordered.length !== inputCount) {\n throw new Error(\n `EmbeddingClient: provider returned ${ordered.length} embeddings for ${inputCount} inputs`,\n );\n }\n return {\n embeddings: ordered.map((d) => d.embedding),\n usage: {\n promptTokens: body.usage?.prompt_tokens ?? 0,\n totalTokens: body.usage?.total_tokens ?? 0,\n },\n };\n }\n\n private resolveApiKey(): string | undefined {\n const envVar = API_KEY_ENV_VARS[this.provider];\n return envVar ? process.env[envVar] : undefined;\n }\n}\n\n/**\n * Cosine similarity between two equal-length vectors. Returns a value in\n * [-1, 1]; higher means more similar. Used by `@almadar-io/agent`'s\n * catalog retrieval to rank organism/atom descriptions against the user\n * request embedding.\n *\n * Defensive: returns 0 when either vector is the zero vector (would\n * otherwise divide by zero).\n */\nexport function cosineSimilarity(a: ReadonlyArray<number>, b: ReadonlyArray<number>): number {\n if (a.length !== b.length) {\n throw new Error(`cosineSimilarity: vector length mismatch (${a.length} vs ${b.length})`);\n }\n let dot = 0;\n let normA = 0;\n let normB = 0;\n for (let i = 0; i < a.length; i++) {\n const av = a[i];\n const bv = b[i];\n dot += av * bv;\n normA += av * av;\n normB += bv * bv;\n }\n if (normA === 0 || normB === 0) return 0;\n return dot / (Math.sqrt(normA) * Math.sqrt(normB));\n}\n","/**\n * Image Client\n *\n * Single-purpose client for image-generation APIs. `openrouter` posts to\n * `POST {baseUrl}/images`; `local` posts an OpenAI-shaped body to\n * `POST {baseUrl}/images/generations` (the MFLUX shim in\n * tools/asset-workflow/python/image_server). Raw `fetch`, no SDK — mirrors\n * `embedding-client.ts`'s shape (provider tables, retry policy, the\n * three response-error surfaces).\n *\n * @packageDocumentation\n */\n\nimport type { JsonValue } from '@almadar/core';\nimport { Agent as UndiciAgent, fetch as undiciFetch, type RequestInit as UndiciRequestInit, type Response as UndiciResponse } from 'undici';\n\nexport type ImageProvider = 'openrouter' | 'local';\n\nconst IMAGE_PROVIDERS: readonly ImageProvider[] = ['openrouter', 'local'];\n\n/** Canonical image-provider resolution, mirrors `resolveDefaultEmbeddingProvider`. */\nexport function resolveDefaultImageProvider(): ImageProvider {\n const v = process.env.IMAGE_PROVIDER;\n return v && (IMAGE_PROVIDERS as readonly string[]).includes(v) ? (v as ImageProvider) : 'openrouter';\n}\n\nexport const OPENROUTER_IMAGE_MODELS = {\n SEEDREAM_5_LITE: 'bytedance-seed/seedream-5-0-lite',\n SEEDREAM_4_5: 'bytedance-seed/seedream-4.5',\n GEMINI_3_1_FLASH_LITE_IMAGE: 'google/gemini-3.1-flash-lite-image',\n GEMINI_2_5_FLASH_IMAGE: 'google/gemini-2.5-flash-image',\n FLUX_2_PRO: 'black-forest-labs/flux.2-pro',\n} as const;\n\nexport const OPENROUTER_IMAGE_DEFAULTS = {\n sheet: OPENROUTER_IMAGE_MODELS.SEEDREAM_5_LITE,\n preview: OPENROUTER_IMAGE_MODELS.GEMINI_3_1_FLASH_LITE_IMAGE,\n} as const;\n\nexport const LOCAL_IMAGE_MODELS = {\n Z_IMAGE_TURBO: 'z-image-turbo',\n FLUX_SCHNELL: 'flux-schnell',\n FLUX_DEV: 'flux-dev',\n FLUX_KONTEXT: 'flux-kontext',\n FLUX2_KLEIN: 'flux2-klein',\n} as const;\n\nexport const LOCAL_IMAGE_DEFAULTS = {\n /** Z-Image-Turbo is the default: public on Hugging Face, no token required (FLUX is gated). */\n sheet: process.env.LOCAL_IMAGE_SHEET_MODEL ?? LOCAL_IMAGE_MODELS.Z_IMAGE_TURBO,\n preview: process.env.LOCAL_IMAGE_PREVIEW_MODEL ?? LOCAL_IMAGE_MODELS.Z_IMAGE_TURBO,\n};\n\nexport interface ImageClientOptions {\n provider?: ImageProvider;\n model?: string;\n apiKey?: string;\n baseUrl?: string;\n fetchImpl?: typeof fetch;\n}\n\n/** An input reference image — `https:` or `data:` URL. Transport only, no fetching/validation. */\nexport interface ImageReference {\n url: string;\n}\n\nexport interface ImageGenerateOptions {\n prompt: string;\n model?: string;\n n?: number;\n size?: `${number}x${number}`;\n resolution?: '512' | '1K' | '2K' | '4K';\n aspectRatio?: string;\n quality?: 'auto' | 'low' | 'medium' | 'high';\n background?: 'auto' | 'transparent' | 'opaque';\n seed?: number;\n /** Diffusion step count (local shim; 1–100). */\n steps?: number;\n /** img2img denoise strength 0–1 (local shim `image_strength`), used with `references`. */\n strength?: number;\n references?: ReadonlyArray<ImageReference>;\n provider?: { order?: string[]; only?: string[]; allowFallbacks?: boolean };\n}\n\nexport interface GeneratedImage {\n bytes: Uint8Array;\n mediaType: string;\n}\n\nexport interface ImageGenerateResult {\n images: GeneratedImage[];\n usage: { cost: number; totalTokens: number };\n model: string;\n dropped: string[];\n}\n\nexport interface ImageModelCapabilities {\n model: string;\n supportedParameters: ReadonlySet<string>;\n raw: JsonValue;\n}\n\n/** Transient-failure retry policy: attempts and backoff (ms) between them. */\nconst IMAGE_MAX_ATTEMPTS = 3;\nconst IMAGE_RETRY_BACKOFF_MS = [500, 2000];\n\nconst DEFAULT_MODELS: Record<ImageProvider, string> = {\n openrouter: OPENROUTER_IMAGE_DEFAULTS.sheet,\n local: LOCAL_IMAGE_DEFAULTS.sheet,\n};\n\nconst DEFAULT_BASE_URLS: Record<ImageProvider, string> = {\n openrouter: 'https://openrouter.ai/api/v1',\n local: 'http://127.0.0.1:8180/v1',\n};\n\nconst API_KEY_ENV_VARS: Record<ImageProvider, string> = {\n openrouter: 'OPEN_ROUTER_API_KEY',\n local: 'LOCAL_IMAGE_API_KEY',\n};\n\n/**\n * Request field → OpenRouter `supported_parameters` name. The single\n * place `generateChecked` reads to decide what to strip — change the\n * mapping here, not at each call site.\n */\nconst IMAGE_CHECKED_PARAM_NAMES = {\n background: 'background',\n seed: 'seed',\n steps: 'steps',\n strength: 'image_strength',\n references: 'input_references',\n} as const;\n\nfunction isJsonObject(value: JsonValue): value is { [key: string]: JsonValue } {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: JsonValue | undefined): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction asNumber(value: JsonValue | undefined): number | undefined {\n return typeof value === 'number' ? value : undefined;\n}\n\nfunction parseJsonBody(provider: ImageProvider, rawBody: string): JsonValue {\n try {\n return JSON.parse(rawBody) as JsonValue;\n } catch {\n throw new Error(`ImageClient: ${provider} returned unparseable body: ${rawBody}`);\n }\n}\n\nfunction decodeBase64(b64: string): Uint8Array {\n if (typeof globalThis.Buffer !== 'undefined') {\n return new Uint8Array(globalThis.Buffer.from(b64, 'base64'));\n }\n const binary = atob(b64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n/**\n * Collects the union of `supported_parameters` across a `.../endpoints`\n * response's `data`/`endpoints` arrays. Returns an empty set (never\n * throws) when the shape doesn't match — the caller still gets `raw`.\n */\nfunction collectSupportedParameters(body: JsonValue): ReadonlySet<string> {\n const params = new Set<string>();\n if (!isJsonObject(body)) return params;\n const candidateArrays: JsonValue[][] = [];\n if (Array.isArray(body.data)) candidateArrays.push(body.data);\n if (Array.isArray(body.endpoints)) candidateArrays.push(body.endpoints);\n for (const arr of candidateArrays) {\n for (const entry of arr) {\n if (!isJsonObject(entry)) continue;\n const sp = entry.supported_parameters;\n if (!Array.isArray(sp)) continue;\n for (const p of sp) if (typeof p === 'string') params.add(p);\n }\n }\n return params;\n}\n\nexport class ImageClient {\n private readonly provider: ImageProvider;\n private readonly model: string;\n private readonly apiKey: string;\n private readonly baseUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly capabilitiesCache = new Map<string, ImageModelCapabilities>();\n\n constructor(opts: ImageClientOptions = {}) {\n this.provider = opts.provider ?? resolveDefaultImageProvider();\n this.model = opts.model ?? DEFAULT_MODELS[this.provider];\n this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URLS[this.provider]).replace(/\\/$/, '');\n this.fetchImpl = opts.fetchImpl ?? fetch;\n\n // The local shim is unauthenticated; a key is required only for hosted providers.\n const apiKey = opts.apiKey ?? process.env[API_KEY_ENV_VARS[this.provider]] ?? '';\n if (!apiKey && this.provider !== 'local') {\n throw new Error(\n `ImageClient: API key for provider '${this.provider}' not found. ` +\n `Set ${API_KEY_ENV_VARS[this.provider]} in your environment or pass options.apiKey.`,\n );\n }\n this.apiKey = apiKey;\n }\n\n /** Generate images. Never strips request fields — `dropped` is always `[]`. */\n async generate(opts: ImageGenerateOptions): Promise<ImageGenerateResult> {\n return this.performGenerate(opts, []);\n }\n\n /**\n * Per-model `.../endpoints` capabilities, memoized per instance per\n * model. `supportedParameters` is the empty set (not a throw) when the\n * response shape doesn't match what's expected.\n */\n async capabilities(modelId?: string): Promise<ImageModelCapabilities> {\n const model = modelId ?? this.model;\n const cached = this.capabilitiesCache.get(model);\n if (cached) return cached;\n\n // The local shim serves no `.../endpoints` metadata; declare what it honors.\n if (this.provider === 'local') {\n const local: ImageModelCapabilities = {\n model,\n supportedParameters: new Set(['seed', IMAGE_CHECKED_PARAM_NAMES.steps, IMAGE_CHECKED_PARAM_NAMES.strength, IMAGE_CHECKED_PARAM_NAMES.references]),\n raw: {},\n };\n this.capabilitiesCache.set(model, local);\n return local;\n }\n\n const response = await this.fetchWithRetry(`${this.baseUrl}/images/models/${model}/endpoints`, {\n method: 'GET',\n headers: this.authHeaders(),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n throw new Error(\n `ImageClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n );\n }\n const body = parseJsonBody(this.provider, await response.text());\n const result: ImageModelCapabilities = { model, supportedParameters: collectSupportedParameters(body), raw: body };\n this.capabilitiesCache.set(model, result);\n return result;\n }\n\n /**\n * Like `generate`, but first reads `capabilities(model)` and strips\n * any of `background`/`seed`/`references` the model doesn't list in\n * `supported_parameters` — their (OpenRouter) names come back in\n * `dropped` rather than being silently swallowed.\n */\n async generateChecked(opts: ImageGenerateOptions): Promise<ImageGenerateResult> {\n const model = opts.model ?? this.model;\n const caps = await this.capabilities(model);\n const dropped: string[] = [];\n const filtered: ImageGenerateOptions = { ...opts, model };\n\n if (filtered.background !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.background)) {\n delete filtered.background;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.background);\n }\n if (filtered.seed !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.seed)) {\n delete filtered.seed;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.seed);\n }\n if (filtered.steps !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.steps)) {\n delete filtered.steps;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.steps);\n }\n if (filtered.strength !== undefined && !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.strength)) {\n delete filtered.strength;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.strength);\n }\n if (\n filtered.references !== undefined &&\n filtered.references.length > 0 &&\n !caps.supportedParameters.has(IMAGE_CHECKED_PARAM_NAMES.references)\n ) {\n delete filtered.references;\n dropped.push(IMAGE_CHECKED_PARAM_NAMES.references);\n }\n\n return this.performGenerate(filtered, dropped);\n }\n\n private async performGenerate(opts: ImageGenerateOptions, dropped: string[]): Promise<ImageGenerateResult> {\n if (opts.size !== undefined && (opts.resolution !== undefined || opts.aspectRatio !== undefined)) {\n throw new Error('ImageClient.generate: `size` cannot be combined with `resolution`/`aspectRatio` — pass one or the other');\n }\n const model = opts.model ?? this.model;\n\n if (this.provider === 'local') {\n return this.performLocalGenerate(opts, model, dropped);\n }\n\n const requestBody: Record<string, JsonValue> = { model, prompt: opts.prompt, output_format: 'png' };\n if (opts.n !== undefined) requestBody.n = opts.n;\n if (opts.size !== undefined) requestBody.size = opts.size;\n if (opts.resolution !== undefined) requestBody.resolution = opts.resolution;\n if (opts.aspectRatio !== undefined) requestBody.aspect_ratio = opts.aspectRatio;\n if (opts.quality !== undefined) requestBody.quality = opts.quality;\n if (opts.background !== undefined) requestBody.background = opts.background;\n if (opts.seed !== undefined) requestBody.seed = opts.seed;\n if (opts.steps !== undefined) requestBody.steps = opts.steps;\n if (opts.references !== undefined && opts.references.length > 0) {\n requestBody.input_references = opts.references.map((r) => ({ type: 'image_url', image_url: { url: r.url } }));\n }\n if (opts.provider !== undefined) {\n const providerField: Record<string, JsonValue> = {};\n if (opts.provider.order !== undefined) providerField.order = opts.provider.order;\n if (opts.provider.only !== undefined) providerField.only = opts.provider.only;\n if (opts.provider.allowFallbacks !== undefined) providerField.allow_fallbacks = opts.provider.allowFallbacks;\n requestBody.provider = providerField;\n }\n\n return this.sendGenerate(`${this.baseUrl}/images`, requestBody, model, dropped);\n }\n\n /**\n * OpenAI-compatible local server (the MFLUX shim in tools/asset-workflow).\n * OpenRouter-only fields are reported in `dropped`, never sent. References\n * ARE sent (as `input_references`) — the shim forwards them to mflux's\n * `image_path`/`image_strength` img2img conditioning.\n */\n private async performLocalGenerate(\n opts: ImageGenerateOptions,\n model: string,\n dropped: string[],\n ): Promise<ImageGenerateResult> {\n const requestBody: Record<string, JsonValue> = { model, prompt: opts.prompt };\n if (opts.n !== undefined) requestBody.n = opts.n;\n if (opts.size !== undefined) requestBody.size = opts.size;\n if (opts.seed !== undefined) requestBody.seed = opts.seed;\n if (opts.steps !== undefined) requestBody.steps = opts.steps;\n if (opts.strength !== undefined) requestBody.image_strength = opts.strength;\n if (opts.references !== undefined && opts.references.length > 0) {\n requestBody.input_references = opts.references.map((r) => ({ type: 'image_url', image_url: { url: r.url } }));\n }\n for (const field of ['resolution', 'aspectRatio', 'quality', 'background'] as const) {\n if (opts[field] !== undefined) dropped.push(field);\n }\n if (opts.provider !== undefined) dropped.push('provider');\n\n return this.sendGenerate(`${this.baseUrl}/images/generations`, requestBody, model, dropped);\n }\n\n private authHeaders(): Record<string, string> {\n return this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {};\n }\n\n private async sendGenerate(\n url: string,\n requestBody: Record<string, JsonValue>,\n model: string,\n dropped: string[],\n ): Promise<ImageGenerateResult> {\n const response = await this.fetchWithRetry(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...this.authHeaders() },\n body: JSON.stringify(requestBody),\n });\n if (!response.ok) {\n const errorText = await response.text().catch(() => '<no body>');\n throw new Error(\n `ImageClient: ${this.provider} returned ${response.status} ${response.statusText}: ${errorText}`,\n );\n }\n const rawBody = await response.text();\n const body = parseJsonBody(this.provider, rawBody);\n if (!isJsonObject(body)) {\n throw new Error(`ImageClient: ${this.provider} returned unparseable body: ${rawBody}`);\n }\n if (isJsonObject(body.error)) {\n const detail = asString(body.error.message) ?? rawBody;\n throw new Error(`ImageClient: ${this.provider} returned an error: ${detail}`);\n }\n if (!Array.isArray(body.data)) {\n throw new Error(`ImageClient: ${this.provider} response has no \\`data\\` array: ${rawBody}`);\n }\n\n const images: GeneratedImage[] = body.data.map((entry, i) => {\n if (!isJsonObject(entry)) {\n throw new Error(`ImageClient: ${this.provider} response data[${i}] is not an object: ${rawBody}`);\n }\n const b64 = asString(entry.b64_json);\n if (!b64) {\n throw new Error(`ImageClient: ${this.provider} response data[${i}] missing \\`b64_json\\`: ${rawBody}`);\n }\n return { bytes: decodeBase64(b64), mediaType: asString(entry.media_type) ?? 'image/png' };\n });\n\n const usageObj = isJsonObject(body.usage) ? body.usage : undefined;\n const usage = {\n cost: asNumber(usageObj?.cost) ?? 0,\n totalTokens: asNumber(usageObj?.total_tokens) ?? 0,\n };\n\n return { images, usage, model, dropped };\n }\n\n /** Retries on network failure or a 5xx status; any other response is returned as-is.\n * The local provider never retries: a failed local generation (e.g. MLX OOM) cannot\n * succeed on an immediate retry, and hammering a memory-pressured machine is harmful. */\n private async fetchWithRetry(url: string, init: RequestInit): Promise<Response | UndiciResponse> {\n const maxAttempts = this.provider === 'local' ? 1 : IMAGE_MAX_ATTEMPTS;\n let lastNetworkError: Error | undefined;\n let response: Response | UndiciResponse | undefined;\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n response = await this.doFetch(url, init);\n } catch (e) {\n response = undefined;\n lastNetworkError = e instanceof Error ? e : new Error(String(e));\n }\n if (response !== undefined && response.status < 500) return response;\n const backoff = IMAGE_RETRY_BACKOFF_MS[attempt - 1];\n if (backoff === undefined || attempt >= maxAttempts) break;\n await new Promise((r) => setTimeout(r, backoff));\n }\n if (response) return response;\n throw lastNetworkError ?? new Error(`ImageClient: ${this.provider} request failed after ${maxAttempts} attempts`);\n }\n\n /** Local generations can exceed Node's built-in 300s headersTimeout (a single\n * MFLUX image routinely takes minutes), so local calls ride npm-undici's own\n * fetch with the transport timers disabled — mirrors `LLMClient.localDispatcher`. */\n private static localDispatcher: UndiciAgent | null = null;\n\n private static getLocalDispatcher(): UndiciAgent {\n if (ImageClient.localDispatcher === null) {\n ImageClient.localDispatcher = new UndiciAgent({ headersTimeout: 0, bodyTimeout: 0 });\n }\n return ImageClient.localDispatcher;\n }\n\n private async doFetch(url: string, init: RequestInit): Promise<Response | UndiciResponse> {\n if (this.provider === 'local' && this.fetchImpl === fetch) {\n const uinit: UndiciRequestInit = {\n method: init.method,\n headers: init.headers as UndiciRequestInit['headers'],\n body: init.body as UndiciRequestInit['body'],\n dispatcher: ImageClient.getLocalDispatcher(),\n };\n return undiciFetch(url, uinit);\n }\n return this.fetchImpl(url, init);\n }\n}\n\nexport const IMAGE_PROVIDER_CATALOG: ReadonlyArray<{ provider: ImageProvider; models: string[]; defaultModel: string }> = [\n {\n provider: 'openrouter',\n models: Object.values(OPENROUTER_IMAGE_MODELS),\n defaultModel: OPENROUTER_IMAGE_DEFAULTS.sheet,\n },\n {\n provider: 'local',\n models: Object.values(LOCAL_IMAGE_MODELS),\n defaultModel: LOCAL_IMAGE_DEFAULTS.sheet,\n },\n];\n","/**\n * Truncation Detector\n *\n * Utilities for detecting when LLM output has been truncated and\n * extracting usable content from partial responses.\n *\n * @packageDocumentation\n */\n\nimport type { LLMFinishReason } from './client.js';\nimport { autoCloseJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type TruncationReason =\n | 'finish_reason'\n | 'json_incomplete'\n | 'bracket_mismatch'\n | 'none';\n\nexport interface TruncationResult {\n isTruncated: boolean;\n reason: TruncationReason;\n partialContent?: string;\n lastCompleteElement?: unknown;\n missingCloseBrackets?: number;\n missingCloseBraces?: number;\n}\n\n// ============================================================================\n// Main Detection Function\n// ============================================================================\n\n/**\n * Detect if an LLM response has been truncated.\n *\n * Analyzes the response content and finish reason to determine if\n * the output was cut off due to token limits or other issues.\n *\n * @param {string} response - The LLM response text\n * @param {LLMFinishReason} finishReason - The finish reason from the LLM\n * @returns {TruncationResult} Detection result with truncation details\n */\nexport function detectTruncation(\n response: string,\n finishReason: LLMFinishReason,\n): TruncationResult {\n if (finishReason === 'length') {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'finish_reason',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n JSON.parse(response);\n return { isTruncated: false, reason: 'none' };\n } catch {\n // JSON is invalid, check if due to truncation\n }\n\n if (finishReason === 'stop' || finishReason === null) {\n const trimmed = response.trim();\n\n const isMidContent =\n trimmed.endsWith(',') ||\n trimmed.endsWith(':') ||\n trimmed.endsWith('\": ') ||\n /:\\s*$/.test(trimmed) ||\n /,\\s*$/.test(trimmed);\n\n if (isMidContent) {\n const bracketInfo = countBrackets(response);\n return {\n isTruncated: true,\n reason: 'json_incomplete',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n try {\n const closed = autoCloseJson(trimmed);\n JSON.parse(closed);\n return { isTruncated: false, reason: 'none' };\n } catch {\n return { isTruncated: false, reason: 'none' };\n }\n }\n\n const bracketInfo = countBrackets(response);\n if (\n bracketInfo.missingCloseBrackets > 0 ||\n bracketInfo.missingCloseBraces > 0\n ) {\n return {\n isTruncated: true,\n reason: 'bracket_mismatch',\n partialContent: response,\n lastCompleteElement: findLastCompleteElement(response),\n missingCloseBrackets: bracketInfo.missingCloseBrackets,\n missingCloseBraces: bracketInfo.missingCloseBraces,\n };\n }\n\n return { isTruncated: false, reason: 'none' };\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction countBrackets(json: string): {\n openBrackets: number;\n closeBrackets: number;\n openBraces: number;\n closeBraces: number;\n missingCloseBrackets: number;\n missingCloseBraces: number;\n} {\n let inString = false;\n let escaped = false;\n let openBrackets = 0;\n let closeBrackets = 0;\n let openBraces = 0;\n let closeBraces = 0;\n\n for (const char of json) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n switch (char) {\n case '[':\n openBrackets++;\n break;\n case ']':\n closeBrackets++;\n break;\n case '{':\n openBraces++;\n break;\n case '}':\n closeBraces++;\n break;\n }\n }\n\n return {\n openBrackets,\n closeBrackets,\n openBraces,\n closeBraces,\n missingCloseBrackets: Math.max(0, openBrackets - closeBrackets),\n missingCloseBraces: Math.max(0, openBraces - closeBraces),\n };\n}\n\nexport function findLastCompleteElement(json: string): unknown | null {\n const autoClosed = autoCloseJson(json);\n try {\n return JSON.parse(autoClosed);\n } catch {\n // Auto-close didn't work\n }\n\n const trimmed = json.trim();\n\n if (trimmed.startsWith('[')) {\n const lastCompleteIndex = findLastCompleteArrayElement(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + ']';\n try {\n return JSON.parse(subset);\n } catch {\n // Continue\n }\n }\n }\n\n if (trimmed.startsWith('{')) {\n const closed = autoCloseJson(trimmed);\n try {\n return JSON.parse(closed);\n } catch {\n const lastCompleteIndex = findLastCompleteObjectProperty(trimmed);\n if (lastCompleteIndex > 0) {\n const subset = trimmed.substring(0, lastCompleteIndex) + '}';\n try {\n return JSON.parse(subset);\n } catch {\n // Give up\n }\n }\n }\n }\n\n return null;\n}\n\nfunction findLastCompleteArrayElement(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCompleteElementEnd = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n if (depth === 1) {\n lastCompleteElementEnd = i + 1;\n }\n } else if (char === ',' && depth === 1) {\n lastCompleteElementEnd = i;\n }\n }\n\n return lastCompleteElementEnd > 0 ? lastCompleteElementEnd : -1;\n}\n\nfunction findLastCompleteObjectProperty(json: string): number {\n let depth = 0;\n let inString = false;\n let escaped = false;\n let lastCommaIndex = -1;\n\n for (let i = 0; i < json.length; i++) {\n const char = json[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n if (char === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n\n if (char === '[' || char === '{') {\n depth++;\n } else if (char === ']' || char === '}') {\n depth--;\n } else if (char === ',' && depth === 1) {\n lastCommaIndex = i;\n }\n }\n\n return lastCommaIndex > 0 ? lastCommaIndex : -1;\n}\n\nexport function isLikelyTruncated(content: string): boolean {\n const trimmed = content.trim();\n if (!trimmed) return false;\n\n const brackets = countBrackets(trimmed);\n if (\n brackets.missingCloseBrackets > 0 ||\n brackets.missingCloseBraces > 0\n ) {\n return true;\n }\n\n const abruptEndings = [\n /,\\s*$/,\n /:\\s*$/,\n /\"\\s*:\\s*$/,\n /\\[\\s*$/,\n /{\\s*$/,\n ];\n\n for (const pattern of abruptEndings) {\n if (pattern.test(trimmed)) return true;\n }\n\n return false;\n}\n","/**\n * LLM Continuation Utility\n *\n * Handles truncated LLM responses with automatic continuation.\n * - Detects truncation via finish_reason and JSON structure\n * - Automatically continues with full context\n * - Merges partial and continuation responses\n * - Salvages partial data if max continuations reached\n *\n * @packageDocumentation\n */\n\nimport { z } from 'zod';\nimport { LLMClient, type LLMFinishReason } from './client.js';\nimport { detectTruncation } from './truncation-detector.js';\nimport { extractJsonFromText, autoCloseJson, isValidJson } from './json-parser.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ContinuationOptions<T> {\n client: LLMClient;\n systemPrompt: string;\n userPrompt: string;\n schema?: z.ZodSchema<T>;\n maxTokens?: number;\n maxContinuations?: number;\n maxRetries?: number;\n buildContinuationPrompt: (\n partialResponse: string,\n attempt: number,\n ) => string;\n continuationSystemPrompt?: string;\n}\n\nexport interface ContinuationResult<T> {\n data: T;\n raw: string;\n continuationCount: number;\n warnings: string[];\n wasSalvaged: boolean;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_MAX_TOKENS = 8192;\nconst DEFAULT_MAX_CONTINUATIONS = 3;\n\n/**\n * Default continuation system prompt.\n * Used when no custom continuationSystemPrompt is provided.\n */\nconst DEFAULT_CONTINUATION_SYSTEM_PROMPT = `You are a JSON continuation assistant. Your ONLY job is to continue generating JSON from where the previous response was truncated.\n\nRules:\n1. Continue from EXACTLY where the previous output stopped\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly with all closing brackets\n4. Do NOT wrap in markdown code blocks\n5. Output ONLY the continuation JSON, nothing else`;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Merge a previous partial response with a continuation.\n *\n * Handles JSON structure merging, removing markdown code blocks,\n * and ensuring proper comma separation between merged parts.\n *\n * @param {string} previous - The previous partial response\n * @param {string} continuation - The continuation to merge\n * @returns {string} The merged response\n */\nexport function mergeResponses(\n previous: string,\n continuation: string,\n): string {\n const trimmedPrev = previous.trimEnd();\n const trimmedCont = continuation.trimStart();\n\n let cleanedCont = trimmedCont\n .replace(/^```json?\\s*/i, '')\n .replace(/```\\s*$/i, '')\n .trim();\n\n if (cleanedCont.startsWith('{')) {\n try {\n const contParsed = JSON.parse(autoCloseJson(cleanedCont));\n const keys = Object.keys(contParsed);\n if (keys.length === 1 && Array.isArray(contParsed[keys[0]])) {\n cleanedCont = contParsed[keys[0]]\n .map((item: unknown) => JSON.stringify(item))\n .join(',\\n');\n }\n } catch {\n // Continue with original cleaning\n }\n }\n\n if (cleanedCont.startsWith('}') || cleanedCont.startsWith(']')) {\n return trimmedPrev + cleanedCont;\n }\n\n const prevEndsWithValue = /[\\}\\]\\\"\\d]$/.test(trimmedPrev);\n const contStartsWithValue = /^[\\{\\[\\\"]/.test(cleanedCont);\n\n if (prevEndsWithValue && contStartsWithValue) {\n return trimmedPrev + ',\\n' + cleanedCont;\n }\n\n return trimmedPrev + cleanedCont;\n}\n\nexport function salvagePartialResponse<T>(rawResponse: string): T | null {\n console.warn('[Continuation] Attempting to salvage partial response');\n\n try {\n const cleanedResponse = extractJsonFromText(rawResponse) || rawResponse;\n const closed = autoCloseJson(cleanedResponse);\n const parsed = JSON.parse(closed) as T;\n console.log('[Continuation] Successfully salvaged partial response');\n return parsed;\n } catch (error) {\n console.error('[Continuation] Could not salvage response:', error);\n }\n\n return null;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Call an LLM with automatic continuation handling.\n *\n * Manages token limits by detecting truncation and making continuation\n * calls until the complete response is received or max continuations reached.\n *\n * @template T - Expected response type\n * @param {ContinuationOptions<T>} options - Continuation call options\n * @returns {Promise<ContinuationResult<T>>} Result with data and continuation metadata\n */\nexport async function callWithContinuation<T>(\n options: ContinuationOptions<T>,\n): Promise<ContinuationResult<T>> {\n const {\n client,\n systemPrompt,\n userPrompt,\n schema,\n maxTokens = DEFAULT_MAX_TOKENS,\n maxContinuations = DEFAULT_MAX_CONTINUATIONS,\n buildContinuationPrompt,\n continuationSystemPrompt = DEFAULT_CONTINUATION_SYSTEM_PROMPT,\n } = options;\n\n let rawResponse = '';\n let continuationCount = 0;\n const warnings: string[] = [];\n let wasSalvaged = false;\n\n console.log('[Continuation] Starting LLM call with continuation support');\n console.log(\n `[Continuation] Max tokens: ${maxTokens}, Max continuations: ${maxContinuations}`,\n );\n\n try {\n const response = await client.callRawWithMetadata({\n systemPrompt,\n userPrompt,\n maxTokens,\n });\n\n rawResponse = extractJsonFromText(response.raw) || response.raw;\n\n console.log(\n `[Continuation] Initial response: ${rawResponse.length} chars, finish_reason: ${response.finishReason}`,\n );\n\n let truncation = detectTruncation(rawResponse, response.finishReason);\n\n while (truncation.isTruncated && continuationCount < maxContinuations) {\n continuationCount++;\n const warningMsg = `Response truncated (${truncation.reason}), continuing (attempt ${continuationCount}/${maxContinuations})`;\n console.log(`[Continuation] ${warningMsg}`);\n warnings.push(warningMsg);\n\n const contPrompt = buildContinuationPrompt(\n rawResponse,\n continuationCount,\n );\n\n const contResponse = await client.callRawWithMetadata({\n systemPrompt: continuationSystemPrompt,\n userPrompt: contPrompt,\n maxTokens,\n });\n\n console.log(\n `[Continuation] Continuation response: ${contResponse.raw.length} chars, finish_reason: ${contResponse.finishReason}`,\n );\n\n const cleanedContResponse =\n extractJsonFromText(contResponse.raw) || contResponse.raw;\n rawResponse = mergeResponses(rawResponse, cleanedContResponse);\n\n truncation = detectTruncation(rawResponse, contResponse.finishReason);\n }\n\n if (\n continuationCount >= maxContinuations &&\n truncation.isTruncated\n ) {\n console.warn(\n `[Continuation] Reached max continuations (${maxContinuations}), attempting to salvage...`,\n );\n warnings.push(\n `Reached max continuations - some content may be incomplete`,\n );\n wasSalvaged = true;\n }\n\n const cleanedResponse =\n extractJsonFromText(rawResponse) || rawResponse;\n let data: T;\n\n try {\n if (isValidJson(cleanedResponse)) {\n data = JSON.parse(cleanedResponse) as T;\n } else {\n const closed = autoCloseJson(cleanedResponse);\n data = JSON.parse(closed) as T;\n if (!wasSalvaged) {\n warnings.push('Response required auto-closing of JSON brackets');\n }\n }\n } catch (parseError) {\n const salvaged = salvagePartialResponse<T>(cleanedResponse);\n if (salvaged) {\n data = salvaged;\n wasSalvaged = true;\n warnings.push('Response was salvaged from partial data');\n } else {\n throw new Error(\n `Failed to parse response after ${continuationCount} continuations: ${parseError}`,\n );\n }\n }\n\n if (schema) {\n try {\n data = schema.parse(data);\n } catch (validationError) {\n console.warn(\n '[Continuation] Schema validation failed:',\n validationError,\n );\n warnings.push(`Schema validation issue: ${validationError}`);\n }\n }\n\n console.log(\n `[Continuation] Complete. Continuations: ${continuationCount}, Warnings: ${warnings.length}`,\n );\n\n return {\n data,\n raw: rawResponse,\n continuationCount,\n warnings,\n wasSalvaged,\n };\n } catch (error) {\n console.error('[Continuation] Error during LLM call:', error);\n throw error;\n }\n}\n\n/**\n * Build a generic continuation prompt.\n *\n * Creates a prompt that instructs the LLM to continue from where\n * a previous truncated response left off.\n *\n * @param {string} context - Original context/prompt\n * @param {string} partialResponse - The partial response generated so far\n * @param {number} attempt - Current continuation attempt number\n * @param {number} [maxAttempts] - Maximum allowed continuation attempts\n * @returns {string} The formatted continuation prompt\n */\nexport function buildGenericContinuationPrompt(\n context: string,\n partialResponse: string,\n attempt: number,\n maxAttempts: number = DEFAULT_MAX_CONTINUATIONS,\n): string {\n return `## CONTINUATION REQUEST (Attempt ${attempt}/${maxAttempts})\n\nYour previous response was truncated. Continue generating from where you left off.\n\n### ORIGINAL CONTEXT\n${context}\n\n### WHAT YOU GENERATED SO FAR\n\\`\\`\\`json\n${partialResponse}\n\\`\\`\\`\n\n### INSTRUCTIONS\n1. Continue from EXACTLY where the response was cut off\n2. Do NOT repeat any content already generated\n3. Complete the JSON structure properly\n4. Do NOT wrap your response in markdown code blocks\n\nContinue generating now:`;\n}\n","/**\n * The tool-calling seam a tool loop needs, and a scripted model for it.\n *\n * `ToolCallingClient` is the one `LLMClient` capability a tool loop uses: one\n * `callWithTools` round-trip. `createScriptedToolClient` replays scripted\n * assistant turns in order, so a loop runs deterministically with no network\n * (unit tests, the verifiers' agent checks). It records every request it gets.\n *\n * @packageDocumentation\n */\n\nimport type { LLMClient, LLMUsage } from './client.js';\nimport type { ChatCompletionMessage, ChatCompletionToolDef } from './tool-call-types.js';\n\nexport type ToolCallingClient = Pick<LLMClient, 'callWithTools'>;\n\nexport interface ScriptedTurn {\n message: ChatCompletionMessage;\n usage?: LLMUsage;\n}\n\nexport interface ScriptedToolClient extends ToolCallingClient {\n readonly requests: ReadonlyArray<{\n messages: ReadonlyArray<ChatCompletionMessage>;\n tools: ReadonlyArray<ChatCompletionToolDef>;\n }>;\n}\n\nexport function createScriptedToolClient(turns: readonly ScriptedTurn[]): ScriptedToolClient {\n const requests: Array<{\n messages: ReadonlyArray<ChatCompletionMessage>;\n tools: ReadonlyArray<ChatCompletionToolDef>;\n }> = [];\n return {\n requests,\n async callWithTools(options) {\n const turn = turns[requests.length];\n requests.push({ messages: [...options.messages], tools: [...options.tools] });\n if (turn === undefined) {\n throw new Error(`scripted tool client: no scripted turn ${requests.length} (script has ${turns.length})`);\n }\n return {\n message: turn.message,\n finishReason: turn.message.tool_calls && turn.message.tool_calls.length > 0 ? 'tool_calls' : 'stop',\n usage: turn.usage ?? null,\n };\n },\n };\n}\n"],"mappings":"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","names":["DEFAULT_MODELS","DEFAULT_BASE_URLS","API_KEY_ENV_VARS","bracketInfo"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almadar/llm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.57.0",
|
|
4
4
|
"description": "Multi-provider LLM client with rate limiting, token tracking, structured outputs, and continuation handling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"src"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@almadar/logger": "^1.
|
|
39
|
+
"@almadar/logger": "^1.15.0",
|
|
40
40
|
"@anthropic-ai/sdk": "^0.52.0",
|
|
41
41
|
"@langchain/anthropic": "^1.3.23",
|
|
42
42
|
"@langchain/core": "^1.1.32",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
"zod": "^3.22.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@almadar/core": "^10.
|
|
50
|
+
"@almadar/core": "^10.114.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@almadar/core": "^10.
|
|
54
|
-
"@almadar/eslint-plugin": "^2.
|
|
53
|
+
"@almadar/core": "^10.114.0",
|
|
54
|
+
"@almadar/eslint-plugin": "^2.21.0",
|
|
55
55
|
"@types/node": "^22.0.0",
|
|
56
56
|
"@typescript-eslint/parser": "8.65.0",
|
|
57
57
|
"@typescript/native": "npm:typescript@^7.0.2",
|
package/src/contracts.ts
CHANGED
|
@@ -37,6 +37,36 @@ interface JsonSchemaDefinition extends ServiceParams {
|
|
|
37
37
|
type ExtractedValue = string | number | boolean | null | ExtractedValue[] | { [key: string]: ExtractedValue };
|
|
38
38
|
type ExtractedData = Record<string, ExtractedValue>;
|
|
39
39
|
|
|
40
|
+
/** One turn of the conversation `call-tools` continues. */
|
|
41
|
+
export interface AgentChatMessage extends ServiceParams {
|
|
42
|
+
role: "user" | "assistant";
|
|
43
|
+
content: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* What the model may use in `call-tools`. Exactly one of:
|
|
48
|
+
* `event` — a declared external input, addressed like a `listens` source
|
|
49
|
+
* (`EVENT`, `Trait.EVENT`, `Orbital.Trait.EVENT`), fired as the caller;
|
|
50
|
+
* `read` — an entity whose rows the caller may read.
|
|
51
|
+
*/
|
|
52
|
+
export interface AgentTool extends ServiceParams {
|
|
53
|
+
event?: string;
|
|
54
|
+
read?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** One tool use inside a `call-tools` run, in order. */
|
|
58
|
+
export interface AgentStep extends ServiceParams {
|
|
59
|
+
/** `input` fired a declared input, `read` read an entity, `invalid` named nothing the tools allow. */
|
|
60
|
+
kind: "input" | "read" | "invalid";
|
|
61
|
+
/** The input address or entity name the model asked for. */
|
|
62
|
+
target: string;
|
|
63
|
+
/** The arguments the model gave, as JSON text. */
|
|
64
|
+
args: string;
|
|
65
|
+
ok: boolean;
|
|
66
|
+
/** Why it did not succeed: the rejection code, failed guard or error. */
|
|
67
|
+
detail?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
/**
|
|
41
71
|
* All call-service actions exposed by the LLM service.
|
|
42
72
|
*/
|
|
@@ -121,6 +151,38 @@ export type LLMServiceActions = {
|
|
|
121
151
|
};
|
|
122
152
|
};
|
|
123
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Answer a request by operating the running app as the caller: the model may
|
|
156
|
+
* fire the declared inputs and read the entities named in `tools`, through the
|
|
157
|
+
* host's ordinary dispatch (guards, policies and listens all apply), until it
|
|
158
|
+
* replies. Hitting `maxSteps` is a failure.
|
|
159
|
+
* @synonyms agent, assistant, tool use, act
|
|
160
|
+
*/
|
|
161
|
+
"call-tools": {
|
|
162
|
+
params: {
|
|
163
|
+
/** The conversation so far, oldest first; the last turn is the request. */
|
|
164
|
+
messages: AgentChatMessage[];
|
|
165
|
+
/** The inputs and entities the model may use. */
|
|
166
|
+
tools: AgentTool[];
|
|
167
|
+
/** Standing instructions for the model. */
|
|
168
|
+
instructions?: string;
|
|
169
|
+
/** Model round-trips allowed before the call fails (default 8). */
|
|
170
|
+
maxSteps?: number;
|
|
171
|
+
/** Model id override. */
|
|
172
|
+
model?: string;
|
|
173
|
+
};
|
|
174
|
+
result: {
|
|
175
|
+
reply: string;
|
|
176
|
+
steps: AgentStep[];
|
|
177
|
+
usage: {
|
|
178
|
+
promptTokens: number;
|
|
179
|
+
completionTokens: number;
|
|
180
|
+
totalTokens: number;
|
|
181
|
+
modelCalls: number;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
124
186
|
/** Generate embeddings for an array of texts. */
|
|
125
187
|
embed: {
|
|
126
188
|
params: {
|
package/src/index.ts
CHANGED
|
@@ -143,8 +143,20 @@ export {
|
|
|
143
143
|
export {
|
|
144
144
|
type LLMServiceActions,
|
|
145
145
|
type LLMServiceContract,
|
|
146
|
+
type MLServiceActions,
|
|
147
|
+
type MLServiceContract,
|
|
148
|
+
type AgentChatMessage,
|
|
149
|
+
type AgentTool,
|
|
150
|
+
type AgentStep,
|
|
146
151
|
} from './contracts.js';
|
|
147
152
|
|
|
153
|
+
export {
|
|
154
|
+
createScriptedToolClient,
|
|
155
|
+
type ToolCallingClient,
|
|
156
|
+
type ScriptedTurn,
|
|
157
|
+
type ScriptedToolClient,
|
|
158
|
+
} from './scripted-tool-client.js';
|
|
159
|
+
|
|
148
160
|
export {
|
|
149
161
|
MasarProvider,
|
|
150
162
|
MasarError,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool-calling seam a tool loop needs, and a scripted model for it.
|
|
3
|
+
*
|
|
4
|
+
* `ToolCallingClient` is the one `LLMClient` capability a tool loop uses: one
|
|
5
|
+
* `callWithTools` round-trip. `createScriptedToolClient` replays scripted
|
|
6
|
+
* assistant turns in order, so a loop runs deterministically with no network
|
|
7
|
+
* (unit tests, the verifiers' agent checks). It records every request it gets.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { LLMClient, LLMUsage } from './client.js';
|
|
13
|
+
import type { ChatCompletionMessage, ChatCompletionToolDef } from './tool-call-types.js';
|
|
14
|
+
|
|
15
|
+
export type ToolCallingClient = Pick<LLMClient, 'callWithTools'>;
|
|
16
|
+
|
|
17
|
+
export interface ScriptedTurn {
|
|
18
|
+
message: ChatCompletionMessage;
|
|
19
|
+
usage?: LLMUsage;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ScriptedToolClient extends ToolCallingClient {
|
|
23
|
+
readonly requests: ReadonlyArray<{
|
|
24
|
+
messages: ReadonlyArray<ChatCompletionMessage>;
|
|
25
|
+
tools: ReadonlyArray<ChatCompletionToolDef>;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createScriptedToolClient(turns: readonly ScriptedTurn[]): ScriptedToolClient {
|
|
30
|
+
const requests: Array<{
|
|
31
|
+
messages: ReadonlyArray<ChatCompletionMessage>;
|
|
32
|
+
tools: ReadonlyArray<ChatCompletionToolDef>;
|
|
33
|
+
}> = [];
|
|
34
|
+
return {
|
|
35
|
+
requests,
|
|
36
|
+
async callWithTools(options) {
|
|
37
|
+
const turn = turns[requests.length];
|
|
38
|
+
requests.push({ messages: [...options.messages], tools: [...options.tools] });
|
|
39
|
+
if (turn === undefined) {
|
|
40
|
+
throw new Error(`scripted tool client: no scripted turn ${requests.length} (script has ${turns.length})`);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
message: turn.message,
|
|
44
|
+
finishReason: turn.message.tool_calls && turn.message.tool_calls.length > 0 ? 'tool_calls' : 'stop',
|
|
45
|
+
usage: turn.usage ?? null,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|