@arnilo/prism 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -1
- package/dist/agent-loops.d.ts +1 -0
- package/dist/agent-loops.js +26 -16
- package/dist/agents.js +2 -3
- package/dist/contracts.d.ts +2 -0
- package/dist/ids.d.ts +2 -0
- package/dist/ids.js +6 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -1
- package/dist/session-stores.js +2 -3
- package/dist/testing/persistence-schema.d.ts +45 -7
- package/dist/testing/persistence-schema.js +138 -24
- package/dist/thinking.d.ts +42 -0
- package/dist/thinking.js +92 -0
- package/dist/tools.js +2 -3
- package/dist/use-case-model.d.ts +63 -0
- package/dist/use-case-model.js +52 -0
- package/docs/a2a.md +4 -2
- package/docs/agent-events.md +10 -15
- package/docs/agent-loops.md +11 -8
- package/docs/coding-agent-tools.md +33 -12
- package/docs/coding-security.md +2 -2
- package/docs/compaction-llm.md +17 -7
- package/docs/compaction-observational-memory.md +28 -4
- package/docs/credential-storage.md +58 -9
- package/docs/credentials-and-redaction.md +1 -1
- package/docs/database-persistence.md +8 -3
- package/docs/host-security.md +10 -6
- package/docs/index.md +23 -20
- package/docs/mcp-tools.md +26 -10
- package/docs/migration.md +146 -2
- package/docs/node-filesystem-config.md +1 -0
- package/docs/node-jsonl-session-store.md +5 -4
- package/docs/postgres-persistence.md +3 -3
- package/docs/provider-caching.md +16 -4
- package/docs/provider-conformance.md +39 -1
- package/docs/provider-packages.md +60 -3
- package/docs/providers/ai-sdk.md +36 -0
- package/docs/providers/kimi.md +124 -61
- package/docs/providers/neuralwatt.md +19 -13
- package/docs/providers/openai.md +56 -13
- package/docs/providers/opencode-go.md +118 -30
- package/docs/providers/openrouter.md +105 -35
- package/docs/providers/zai.md +94 -45
- package/docs/release-and-install.md +47 -49
- package/docs/review-coverage-2026-07-17-provider-validation.md +192 -0
- package/docs/runs-and-usage.md +1 -1
- package/docs/sqlite-persistence.md +2 -2
- package/docs/structured-output.md +1 -1
- package/docs/thinking-and-reasoning.md +98 -0
- package/docs/tool-execution-primitives.md +3 -3
- package/docs/tools.md +15 -0
- package/docs/use-case-model-selection.md +109 -0
- package/docs/workflow-orchestration-primitives.md +1 -0
- package/docs/workflows.md +17 -10
- package/docs/working-and-semantic-memory.md +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,34 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.0.6] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Caller-gated model discovery: `listOpenAIModels`, `listKimiModels`, `listZaiModels`, `listOpenRouterModels`, and `listOpenCodeGoModels`. Provider setup remains network-free; hosts explicitly fetch and register current models.
|
|
13
|
+
- Shared `ThinkingLevel` helpers and use-case model bindings. Background compaction and observational-memory jobs can use an explicit provider/model or a supplied session-model fallback.
|
|
14
|
+
- Opt-in sequential artifact-loop tools: `loop: { strategy: "generate-validate-revise", toolCalls: "bounded" }`. Tool rounds use existing authorization/redaction/ledger paths, share `maxToolRounds` across candidates, and fail with `artifact_failed` metadata `{ reason: "tool_round_limit" }` after exhaustion.
|
|
15
|
+
- Checksummed SQLite/PostgreSQL migration histories and catalog-shape verification, bounded JSON Schema compilation LRU, and public `assertFiniteVector` validation.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Provider packages now document and implement current cache, reasoning, streaming, and discovery behavior. OpenAI Responses replay/function-call/SSE argument handling is corrected; Kimi adds optional Moonshot support; Z.AI and OpenCode Go catalogs/routes were refreshed; OpenRouter discovery/reasoning and NeuralWatt thinking controls are hardened. AI SDK remains host-model-owned.
|
|
20
|
+
- Workflow definitions now require a non-empty `revision`; cancellation requires exact ownership and the current workflow definition. All workflow limits have finite hard caps.
|
|
21
|
+
- Coding tools now enforce bounded streamed reads, write/edit inputs, shell wall time, total output, and spill-file lifecycle. Custom coding operation interfaces now receive bounded read/stat/write/edit options and abort signals.
|
|
22
|
+
- Encrypted credential helpers `encryptBytes`, `decryptBytes`, and envelope rotation are asynchronous. Existing credential files must meet restrictive Unix permission requirements. Linux Secret Service/GNOME Keyring byte-array reads are accepted by the keychain store.
|
|
23
|
+
- MCP Streamable HTTP requires HTTPS and explicit `allowedOrigins`; loopback HTTP requires explicit opt-in. Discovery, schemas, results, and response bodies are bounded.
|
|
24
|
+
- Compaction and observational-memory workers now have finite turn/call/transcript/error budgets. A2A streaming uses strict incremental UTF-8 and LF/CRLF SSE parsing.
|
|
25
|
+
- Generated Prism, workflow, and evaluation IDs use cryptographic UUIDs; non-finite embedding vectors now fail before scoring or persistence.
|
|
26
|
+
|
|
27
|
+
### Security
|
|
28
|
+
|
|
29
|
+
- Fixed cross-owner workflow cancellation and duplicate active-run overwrite risks.
|
|
30
|
+
- Added fail-closed limits and validation at file, process, credential, MCP, migration, schema, vector, provider-worker, and A2A trust boundaries.
|
|
31
|
+
|
|
32
|
+
### Upgrade notes
|
|
33
|
+
|
|
34
|
+
- Finish or deliberately migrate pre-0.0.6 workflow runs/checkpoints before upgrading: their definition hashes lack the required revision.
|
|
35
|
+
- Update workflow definitions with `revision`, cancellation callers with `workflow` plus exact ownership, MCP HTTP configs with `allowedOrigins`, and custom coding/credential integrations for the changed interfaces above.
|
|
9
36
|
|
|
10
37
|
## [0.0.5] - 2026-07-16
|
|
11
38
|
|
package/dist/agent-loops.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export declare function generateValidateReviseLoop(opts: {
|
|
|
6
6
|
readonly parser?: ArtifactParser<unknown>;
|
|
7
7
|
readonly repairer?: ArtifactRepairer<unknown>;
|
|
8
8
|
readonly maxRevisions?: number;
|
|
9
|
+
readonly toolCalls?: "disabled" | "bounded";
|
|
9
10
|
}): AgentLoopStrategy;
|
|
10
11
|
export declare function resolveToolConcurrency(options: {
|
|
11
12
|
loop?: AgentLoopStrategy | AgentLoopOptions;
|
package/dist/agent-loops.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { inputMessages } from "./input.js";
|
|
2
|
+
import { createId } from "./ids.js";
|
|
2
3
|
function throwIfAborted(signal) {
|
|
3
4
|
if (signal.aborted)
|
|
4
5
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
@@ -55,10 +56,8 @@ function defaultRepairer() {
|
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
// ponytail: GenerateValidateReviseLoop reuses LoopContext primitives only —
|
|
58
|
-
// no provider/retry/store/event re-implementation.
|
|
59
|
-
//
|
|
60
|
-
// generate→validate→revise; tool coupling deferred). Phase 28 fires
|
|
61
|
-
// artifact_* events at the marked seams (noop here).
|
|
59
|
+
// no provider/retry/store/event re-implementation. Bounded artifact tools use
|
|
60
|
+
// same dispatcher at concurrency one; add parallelism only with ordering need.
|
|
62
61
|
export function generateValidateReviseLoop(opts) {
|
|
63
62
|
const max = opts.maxRevisions ?? 3;
|
|
64
63
|
const repairer = opts.repairer ?? defaultRepairer();
|
|
@@ -68,12 +67,14 @@ export function generateValidateReviseLoop(opts) {
|
|
|
68
67
|
let usage;
|
|
69
68
|
let nextInput = ctx.input;
|
|
70
69
|
let pendingHistory = [];
|
|
71
|
-
|
|
70
|
+
let toolRounds = 0;
|
|
71
|
+
let attempts = 0;
|
|
72
|
+
for (let turn = 1; attempts <= max; turn += 1) {
|
|
72
73
|
throwIfAborted(ctx.signal);
|
|
73
74
|
ctx.emit({ type: "turn_started", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
74
75
|
const request = await ctx.assemble(nextInput, undefined, turn);
|
|
75
76
|
throwIfAborted(ctx.signal);
|
|
76
|
-
const { content, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
77
|
+
const { content, calls, messageId, started, usage: turnUsage } = await ctx.generate(request);
|
|
77
78
|
usage = turnUsage ?? usage;
|
|
78
79
|
if (pendingHistory.length > 0) {
|
|
79
80
|
ctx.history.push(...pendingHistory);
|
|
@@ -88,10 +89,17 @@ export function generateValidateReviseLoop(opts) {
|
|
|
88
89
|
ctx.emit({ type: "message_finished", sessionId: ctx.sessionId, runId: ctx.runId, message });
|
|
89
90
|
}
|
|
90
91
|
ctx.emit({ type: "turn_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn });
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
if (opts.toolCalls === "bounded" && calls.length > 0) {
|
|
93
|
+
if (toolRounds >= ctx.maxToolRounds) {
|
|
94
|
+
const result = { ok: false, errors: [{ message: "maximum tool rounds exceeded" }], metadata: { reason: "tool_round_limit" } };
|
|
95
|
+
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt: attempts + 1, result });
|
|
96
|
+
return usage;
|
|
97
|
+
}
|
|
98
|
+
toolRounds += 1;
|
|
99
|
+
await dispatchToolCallsInOrder(calls, { ...ctx, toolConcurrency: 1 });
|
|
100
|
+
nextInput = [];
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
95
103
|
const artifactCtx = {
|
|
96
104
|
sessionId: ctx.sessionId,
|
|
97
105
|
runId: ctx.runId,
|
|
@@ -99,13 +107,17 @@ export function generateValidateReviseLoop(opts) {
|
|
|
99
107
|
signal: ctx.signal,
|
|
100
108
|
metadata: ctx.metadata,
|
|
101
109
|
};
|
|
110
|
+
const text = content
|
|
111
|
+
.filter((b) => b.type === "text")
|
|
112
|
+
.map((b) => b.text)
|
|
113
|
+
.join("");
|
|
102
114
|
const parsed = opts.parser
|
|
103
115
|
? await opts.parser(text, artifactCtx)
|
|
104
116
|
: { ok: true, value: text };
|
|
105
117
|
// Parse failure ends the loop silently (terminal parse errors stay on `error`).
|
|
106
118
|
if (!parsed.ok || parsed.value === undefined)
|
|
107
119
|
return usage;
|
|
108
|
-
const attempt =
|
|
120
|
+
const attempt = ++attempts;
|
|
109
121
|
ctx.emit({ type: "artifact_validation_started", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt });
|
|
110
122
|
const result = await opts.validator(parsed.value, artifactCtx);
|
|
111
123
|
ctx.emit({ type: "artifact_validation_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
@@ -113,7 +125,7 @@ export function generateValidateReviseLoop(opts) {
|
|
|
113
125
|
ctx.emit({ type: "artifact_finished", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
114
126
|
return usage;
|
|
115
127
|
}
|
|
116
|
-
if (
|
|
128
|
+
if (attempt > max) {
|
|
117
129
|
ctx.emit({ type: "artifact_failed", sessionId: ctx.sessionId, runId: ctx.runId, turn, attempt, result });
|
|
118
130
|
return usage;
|
|
119
131
|
}
|
|
@@ -124,7 +136,6 @@ export function generateValidateReviseLoop(opts) {
|
|
|
124
136
|
await ctx.appendMessage(message);
|
|
125
137
|
pendingHistory = repairMessages;
|
|
126
138
|
nextInput = repairMessages;
|
|
127
|
-
continue;
|
|
128
139
|
}
|
|
129
140
|
return usage;
|
|
130
141
|
},
|
|
@@ -195,13 +206,12 @@ export function resolveLoop(options, config) {
|
|
|
195
206
|
parser: loop.parser,
|
|
196
207
|
repairer: loop.repairer,
|
|
197
208
|
maxRevisions: loop.maxRevisions,
|
|
209
|
+
toolCalls: loop.toolCalls,
|
|
198
210
|
});
|
|
199
211
|
}
|
|
200
212
|
throw new Error(`Unknown agent loop strategy: ${strategy}`);
|
|
201
213
|
}
|
|
202
214
|
return loop;
|
|
203
215
|
}
|
|
204
|
-
|
|
205
|
-
return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
206
|
-
}
|
|
216
|
+
const randomId = createId;
|
|
207
217
|
//# sourceMappingURL=agent-loops.js.map
|
package/dist/agents.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentRunError } from "./contracts.js";
|
|
2
2
|
import { resolveLoop, resolveToolConcurrency } from "./agent-loops.js";
|
|
3
|
+
import { createId } from "./ids.js";
|
|
3
4
|
import { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
4
5
|
import { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
|
|
5
6
|
import { assembleProviderInput } from "./input.js";
|
|
@@ -800,7 +801,5 @@ function createUsageAccumulator() {
|
|
|
800
801
|
},
|
|
801
802
|
};
|
|
802
803
|
}
|
|
803
|
-
|
|
804
|
-
return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
805
|
-
}
|
|
804
|
+
const randomId = createId;
|
|
806
805
|
//# sourceMappingURL=agents.js.map
|
package/dist/contracts.d.ts
CHANGED
|
@@ -1440,6 +1440,8 @@ export type AgentLoopOptions = {
|
|
|
1440
1440
|
readonly parser?: ArtifactParser<unknown>;
|
|
1441
1441
|
readonly repairer?: ArtifactRepairer<unknown>;
|
|
1442
1442
|
readonly maxRevisions?: number;
|
|
1443
|
+
/** Dispatch provider tool calls in artifact turns. Default `"disabled"`; `"bounded"` uses RunOptions.maxToolRounds sequentially. */
|
|
1444
|
+
readonly toolCalls?: "disabled" | "bounded";
|
|
1443
1445
|
/** Native provider JSON-schema output. Ignored when `structuredOutputMode` is `artifact-loop`. */
|
|
1444
1446
|
readonly structuredOutput?: StructuredOutputOptions;
|
|
1445
1447
|
/** `native` maps schema to capable providers; `artifact-loop` keeps repair turns only. */
|
package/dist/ids.d.ts
ADDED
package/dist/ids.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,10 @@ export { assertStructuredOutputRequestSupported, DEFAULT_MAX_STRUCTURED_OUTPUT_N
|
|
|
30
30
|
export { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
31
31
|
export { createProviderRequestPolicyChain, createSessionCachePolicy, mergeProviderRequestOptions } from "./provider-request-policy.js";
|
|
32
32
|
export type { SessionCachePolicyOptions } from "./provider-request-policy.js";
|
|
33
|
+
export { THINKING_LEVELS, applyThinkingLevel, isThinkingLevel, normalizeThinkingLevel, thinkingCompatFor, thinkingFamilyForModel, } from "./thinking.js";
|
|
34
|
+
export type { ThinkingCompatFamily, ThinkingLevel } from "./thinking.js";
|
|
35
|
+
export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
|
|
36
|
+
export type { ResolveUseCaseModelInput, ResolvedUseCaseModel, UseCaseModelBinding, } from "./use-case-model.js";
|
|
33
37
|
export { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
|
|
34
38
|
export type { ComposeSystemPromptOptions } from "./system-prompts.js";
|
|
35
39
|
export type { ModelRegistry, ModelRegistryOptions } from "./models.js";
|
|
@@ -67,5 +71,5 @@ export type { DispatchToolCallOptions, ToolArgumentValidationError, ToolArgument
|
|
|
67
71
|
export type { DuplicateRegistrationOptions, DuplicateRegistrationPolicy } from "./registry-options.js";
|
|
68
72
|
export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
|
|
69
73
|
export declare const name = "prism";
|
|
70
|
-
export declare const version = "0.0.
|
|
74
|
+
export declare const version = "0.0.6";
|
|
71
75
|
export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,8 @@ export { authMethodKey, defineProviderPackage, systemPromptContributionKey } fro
|
|
|
18
18
|
export { assertStructuredOutputRequestSupported, DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH, DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES, modelSupportsStructuredOutput, resolveRunProviderOptions, StructuredOutputError, validateStructuredOutputOptions, } from "./structured-output.js";
|
|
19
19
|
export { createProviderTurnMetadata, readProviderHttpStatus } from "./observability.js";
|
|
20
20
|
export { createProviderRequestPolicyChain, createSessionCachePolicy, mergeProviderRequestOptions } from "./provider-request-policy.js";
|
|
21
|
+
export { THINKING_LEVELS, applyThinkingLevel, isThinkingLevel, normalizeThinkingLevel, thinkingCompatFor, thinkingFamilyForModel, } from "./thinking.js";
|
|
22
|
+
export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
|
|
21
23
|
export { composeSystemPrompt, mergeSystemPromptConfig } from "./system-prompts.js";
|
|
22
24
|
export { definePrismManifest, parsePrismManifest } from "./manifests.js";
|
|
23
25
|
export { assertDeclaredMediaTypeMatches, assertMediaBlocksWithinBounds, assertMessagesSupportModelCapabilities, assertModelSupportsContentBlocks, assertSsrfAllowedUrl, collectMessageContentBlocks, contentBlockInputModality, DEFAULT_MAX_AUDIO_DURATION_MS, DEFAULT_MAX_MEDIA_ITEM_BYTES, DEFAULT_MAX_MEDIA_ITEMS_PER_REQUEST, DEFAULT_MAX_MEDIA_REQUEST_BYTES, DEFAULT_MEDIA_FETCH_TIMEOUT_MS, loadBoundedBinaryResource, MediaContentError, MODEL_INPUT_CAPABILITIES, resolveMediaContentBlock, resolveMediaContentBlocks, sniffMediaMimeType, UnsupportedModalityError, } from "./content.js";
|
|
@@ -37,6 +39,6 @@ export { resolveInstructionInjectors, runInstructionInjectors } from "./instruct
|
|
|
37
39
|
export { createToolRegistry, dispatchToolCall, filterTools, createToolParameterValidator } from "./tools.js";
|
|
38
40
|
export { dispatchToolCallsInOrder, generateValidateReviseLoop, isAgentLoopOptions, resolveLoop, resolveToolConcurrency, singleShotLoop } from "./agent-loops.js";
|
|
39
41
|
export const name = "prism";
|
|
40
|
-
export const version = "0.0.
|
|
42
|
+
export const version = "0.0.6";
|
|
41
43
|
export const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
|
42
44
|
//# sourceMappingURL=index.js.map
|
package/dist/session-stores.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SESSION_APPEND_CONFLICT_CODE, SessionAppendConflictError } from "./contracts.js";
|
|
2
|
+
import { createId } from "./ids.js";
|
|
2
3
|
export function createSessionEntry(options) {
|
|
3
4
|
const { createId, now, ...entry } = options;
|
|
4
5
|
return {
|
|
@@ -171,7 +172,5 @@ function indexEntries(entries) {
|
|
|
171
172
|
}
|
|
172
173
|
return { byId, parentIds };
|
|
173
174
|
}
|
|
174
|
-
|
|
175
|
-
return `${prefix}_${globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
176
|
-
}
|
|
175
|
+
const randomId = createId;
|
|
177
176
|
//# sourceMappingURL=session-stores.js.map
|
|
@@ -7,6 +7,8 @@ export interface PersistenceColumnDefinition {
|
|
|
7
7
|
readonly name: string;
|
|
8
8
|
readonly type: PersistenceColumnType;
|
|
9
9
|
readonly nullable?: boolean;
|
|
10
|
+
/** Portable SQL default literal, if the schema requires one. */
|
|
11
|
+
readonly defaultValue?: string;
|
|
10
12
|
/** Column participates in tenant isolation boundaries when true. */
|
|
11
13
|
readonly tenantScoped?: boolean;
|
|
12
14
|
}
|
|
@@ -42,6 +44,8 @@ export interface PersistenceMigrationStep {
|
|
|
42
44
|
readonly version: number;
|
|
43
45
|
readonly name: string;
|
|
44
46
|
readonly description?: string;
|
|
47
|
+
/** SHA-256 of the canonical checked-in migration schema content. */
|
|
48
|
+
readonly checksum: string;
|
|
45
49
|
}
|
|
46
50
|
/** Versioned migration expectations shared by production database adapters. */
|
|
47
51
|
export interface PersistenceMigrationContract {
|
|
@@ -74,6 +78,46 @@ export declare function tenantScopedUniqueKey(baseColumns: readonly string[], te
|
|
|
74
78
|
export declare function assertPersistenceSchemaModel(model: PersistenceSchemaModel): void;
|
|
75
79
|
/** Assert migration steps are strictly increasing and end at the target schema version. */
|
|
76
80
|
export declare function assertPersistenceMigrationContract(contract: PersistenceMigrationContract): void;
|
|
81
|
+
export type PersistenceSchemaDialect = "sqlite" | "postgres";
|
|
82
|
+
export interface PersistenceSchemaShapeColumn {
|
|
83
|
+
readonly name: string;
|
|
84
|
+
readonly type: string;
|
|
85
|
+
readonly nullable: boolean;
|
|
86
|
+
readonly defaultValue?: string;
|
|
87
|
+
}
|
|
88
|
+
export interface PersistenceSchemaShapeForeignKey {
|
|
89
|
+
readonly columns: readonly string[];
|
|
90
|
+
readonly referencesTable: string;
|
|
91
|
+
readonly referencesColumns: readonly string[];
|
|
92
|
+
}
|
|
93
|
+
export interface PersistenceSchemaShapeTable {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
readonly columns: readonly PersistenceSchemaShapeColumn[];
|
|
96
|
+
readonly primaryKey: readonly string[];
|
|
97
|
+
readonly uniqueKeys: readonly (readonly string[])[];
|
|
98
|
+
readonly foreignKeys: readonly PersistenceSchemaShapeForeignKey[];
|
|
99
|
+
}
|
|
100
|
+
export interface PersistenceSchemaShapeIndex {
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly table: string;
|
|
103
|
+
readonly columns: readonly string[];
|
|
104
|
+
readonly unique: boolean;
|
|
105
|
+
}
|
|
106
|
+
export interface PersistenceSchemaShape {
|
|
107
|
+
readonly tables: readonly PersistenceSchemaShapeTable[];
|
|
108
|
+
readonly indexes: readonly PersistenceSchemaShapeIndex[];
|
|
109
|
+
}
|
|
110
|
+
/** Compare bounded dialect catalog output against every required schema-v3 detail. */
|
|
111
|
+
export declare function assertPersistenceSchemaShape(shape: PersistenceSchemaShape, dialect: PersistenceSchemaDialect, model?: PersistenceSchemaModel): void;
|
|
112
|
+
export interface AppliedPersistenceMigration {
|
|
113
|
+
readonly name: string;
|
|
114
|
+
readonly version: string;
|
|
115
|
+
readonly checksum: string | null;
|
|
116
|
+
}
|
|
117
|
+
/** Reject altered migration history before any new DDL or runtime write. */
|
|
118
|
+
export declare function assertAppliedPersistenceMigrations(contract: PersistenceMigrationContract, applied: readonly AppliedPersistenceMigration[]): {
|
|
119
|
+
readonly legacyChecksums: boolean;
|
|
120
|
+
};
|
|
77
121
|
/**
|
|
78
122
|
* Assert a dialect-local adapter exposes the canonical table and index names.
|
|
79
123
|
* Adapters pass the table/index names their migration runner created.
|
|
@@ -82,13 +126,7 @@ export declare function assertAdapterSchemaMatchesModel(adapterTables: readonly
|
|
|
82
126
|
/** Guard adapter SQL tests: reject obvious value interpolation into statement text. */
|
|
83
127
|
export declare function assertParameterizedQuery(sql: string, boundValues: readonly unknown[]): void;
|
|
84
128
|
/** Simulate migration up + reopen: applied steps must match the contract in order. */
|
|
85
|
-
export declare function assertMigrationUpAndReopen(contract: PersistenceMigrationContract, appliedAfterUp: readonly
|
|
86
|
-
readonly name: string;
|
|
87
|
-
readonly version: string;
|
|
88
|
-
}[], appliedAfterReopen: readonly {
|
|
89
|
-
readonly name: string;
|
|
90
|
-
readonly version: string;
|
|
91
|
-
}[]): void;
|
|
129
|
+
export declare function assertMigrationUpAndReopen(contract: PersistenceMigrationContract, appliedAfterUp: readonly AppliedPersistenceMigration[], appliedAfterReopen: readonly AppliedPersistenceMigration[]): void;
|
|
92
130
|
export interface PersistenceQueryConformanceFixture {
|
|
93
131
|
readonly seedEntries: (entries: readonly SessionEntry[]) => Promise<void> | void;
|
|
94
132
|
readonly queryEntries: (query: SessionEntryQuery) => Promise<PersistencePage<SessionEntry>>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
// ponytail: dialect-neutral persistence schema model and migration contracts for
|
|
2
3
|
// SQLite/PostgreSQL adapter packages (Plan 056 Task 1). SQL stays package-local;
|
|
3
4
|
// this module defines the shared table/index/pagination/migration expectations
|
|
@@ -107,7 +108,7 @@ export function createPersistenceSchemaModel() {
|
|
|
107
108
|
{ name: "run_id", type: "text", nullable: true },
|
|
108
109
|
{ name: "timestamp", type: "timestamp" },
|
|
109
110
|
{ name: "kind", type: "text" },
|
|
110
|
-
{ name: "schema_version", type: "integer" },
|
|
111
|
+
{ name: "schema_version", type: "integer", nullable: true },
|
|
111
112
|
{ name: "message", type: "json", nullable: true },
|
|
112
113
|
{ name: "event", type: "json", nullable: true },
|
|
113
114
|
{ name: "model", type: "json", nullable: true },
|
|
@@ -156,7 +157,6 @@ export function createPersistenceSchemaModel() {
|
|
|
156
157
|
...TENANT_COLUMNS,
|
|
157
158
|
{ name: "metadata", type: "json", nullable: true },
|
|
158
159
|
],
|
|
159
|
-
uniqueKeys: [["tenant_id", "idempotency_key"]],
|
|
160
160
|
foreignKeys: [{ columns: ["session_id"], referencesTable: "prism_sessions", referencesColumns: ["id"] }],
|
|
161
161
|
},
|
|
162
162
|
{
|
|
@@ -210,7 +210,7 @@ export function createPersistenceSchemaModel() {
|
|
|
210
210
|
{ name: "session_id", type: "text" },
|
|
211
211
|
{ name: "run_id", type: "text", nullable: true },
|
|
212
212
|
{ name: "entry_id", type: "text", nullable: true },
|
|
213
|
-
{ name: "scope", type: "text" },
|
|
213
|
+
{ name: "scope", type: "text", defaultValue: "'run_total'" },
|
|
214
214
|
{ name: "turn", type: "integer", nullable: true },
|
|
215
215
|
{ name: "attempt", type: "integer", nullable: true },
|
|
216
216
|
{ name: "usage", type: "json" },
|
|
@@ -235,7 +235,9 @@ export function createPersistenceSchemaModel() {
|
|
|
235
235
|
{ name: "evaluation_ids", type: "json" },
|
|
236
236
|
{ name: "created_at", type: "timestamp" },
|
|
237
237
|
{ name: "created_by", type: "text", nullable: true },
|
|
238
|
-
|
|
238
|
+
{ name: "tenant_id", type: "text", tenantScoped: true },
|
|
239
|
+
{ name: "account_id", type: "text", nullable: true, tenantScoped: true },
|
|
240
|
+
{ name: "user_id", type: "text", nullable: true, tenantScoped: true },
|
|
239
241
|
{ name: "metadata", type: "json", nullable: true },
|
|
240
242
|
],
|
|
241
243
|
foreignKeys: [{ columns: ["run_id"], referencesTable: "prism_runs", referencesColumns: ["id"] }],
|
|
@@ -281,7 +283,7 @@ export function createPersistenceSchemaModel() {
|
|
|
281
283
|
{ name: "prism_session_entries_session_run_ts_idx", table: "prism_session_entries", columns: ["session_id", "run_id", "timestamp"], purpose: "run-scoped entry listing" },
|
|
282
284
|
{ name: "prism_session_entries_session_ts_id_idx", table: "prism_session_entries", columns: ["session_id", "timestamp", "id"], purpose: "cursor pagination without full scans" },
|
|
283
285
|
{ name: "prism_session_entries_session_id_idx", table: "prism_session_entries", columns: ["session_id", "id"], purpose: "append parent validation and recursive branch reads" },
|
|
284
|
-
{ name: "prism_session_append_idempotency_unique", table: "prism_session_append_idempotency", columns: ["session_id", "expected_parent_id", "idempotency_key"],
|
|
286
|
+
{ name: "prism_session_append_idempotency_unique", table: "prism_session_append_idempotency", columns: ["session_id", "expected_parent_id", "idempotency_key"], purpose: "append retry deduplication (primary key enforces uniqueness)" },
|
|
285
287
|
{ name: "prism_runs_session_started_idx", table: "prism_runs", columns: ["session_id", "started_at", "id"], purpose: "run history pagination" },
|
|
286
288
|
{ name: "prism_runs_branch_started_idx", table: "prism_runs", columns: ["branch_id", "started_at", "id"], purpose: "branch-scoped runs" },
|
|
287
289
|
{ name: "prism_runs_tenant_idempotency_unique", table: "prism_runs", columns: ["tenant_id", "idempotency_key"], unique: true, purpose: "run-level idempotency deduplication per tenant" },
|
|
@@ -296,19 +298,40 @@ export function createPersistenceSchemaModel() {
|
|
|
296
298
|
{ name: "prism_run_feedback_run_created_idx", table: "prism_run_feedback", columns: ["run_id", "created_at", "id"], purpose: "run feedback lookup" },
|
|
297
299
|
{ name: "prism_run_feedback_trace_created_idx", table: "prism_run_feedback", columns: ["trace_id", "created_at", "id"], purpose: "trace feedback lookup" },
|
|
298
300
|
{ name: "prism_agent_definitions_name_version_idx", table: "prism_agent_definitions", columns: ["name", "version"], purpose: "definition lookup" },
|
|
299
|
-
{ name: "prism_migrations_name_version_idx", table: "prism_migrations", columns: ["name", "version"],
|
|
301
|
+
{ name: "prism_migrations_name_version_idx", table: "prism_migrations", columns: ["name", "version"], purpose: "applied-migration lookup (table constraint enforces uniqueness)" },
|
|
300
302
|
],
|
|
301
303
|
};
|
|
302
304
|
}
|
|
305
|
+
function migrationStep(version, name, description) {
|
|
306
|
+
const model = createPersistenceSchemaModel();
|
|
307
|
+
const content = version === 1
|
|
308
|
+
? {
|
|
309
|
+
tables: model.tables
|
|
310
|
+
.filter((table) => table.name !== "prism_run_feedback")
|
|
311
|
+
.map((table) => table.name === "prism_usage"
|
|
312
|
+
? { ...table, columns: table.columns.filter((column) => !["scope", "turn", "attempt"].includes(column.name)) }
|
|
313
|
+
: table),
|
|
314
|
+
indexes: model.indexes.filter((index) => !index.name.startsWith("prism_usage_session_scope_") && !index.name.startsWith("prism_run_feedback_")),
|
|
315
|
+
}
|
|
316
|
+
: version === 2
|
|
317
|
+
? { table: "prism_usage", columns: ["scope", "turn", "attempt"], indexes: ["prism_usage_session_scope_recorded_idx"] }
|
|
318
|
+
: { tables: ["prism_run_feedback"], indexes: model.indexes.filter((index) => index.name.startsWith("prism_run_feedback_")).map((index) => index.name) };
|
|
319
|
+
return {
|
|
320
|
+
version,
|
|
321
|
+
name,
|
|
322
|
+
description,
|
|
323
|
+
checksum: createHash("sha256").update(JSON.stringify({ version, name, content })).digest("hex"),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
303
326
|
/** Canonical migration contract for production adapters. */
|
|
304
327
|
export function createPersistenceMigrationContract() {
|
|
305
328
|
return {
|
|
306
329
|
targetSchemaVersion: PERSISTENCE_SCHEMA_VERSION,
|
|
307
330
|
appliedMigrationsTable: "prism_migrations",
|
|
308
331
|
steps: [
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
332
|
+
migrationStep(1, "001_init", "Create core session, branch, entry, idempotency, run, ledger, and migration tables."),
|
|
333
|
+
migrationStep(2, "002_usage_scope", "Distinguish provider-turn usage from aggregate run totals."),
|
|
334
|
+
migrationStep(3, "003_run_feedback", "Add immutable ownership-scoped run/trace feedback and evaluation links."),
|
|
312
335
|
],
|
|
313
336
|
lockGuidance: "Acquire a dialect-specific migration lock before applying steps (PostgreSQL advisory lock; SQLite exclusive transaction). Only one process should migrate at a time.",
|
|
314
337
|
leastPrivilegeGuidance: "Run migrations with a DDL-capable role; use a separate least-privilege runtime role limited to INSERT/SELECT/UPDATE on adapter tables. Never grant migration credentials to the agent runtime.",
|
|
@@ -387,6 +410,8 @@ export function assertPersistenceMigrationContract(contract) {
|
|
|
387
410
|
throw new Error(`Migration steps must be strictly increasing; ${step.name} is out of order`);
|
|
388
411
|
if (names.has(step.name))
|
|
389
412
|
throw new Error(`Duplicate migration step name: ${step.name}`);
|
|
413
|
+
if (!/^[a-f0-9]{64}$/.test(step.checksum))
|
|
414
|
+
throw new Error(`Migration step ${step.name} must have a SHA-256 checksum`);
|
|
390
415
|
names.add(step.name);
|
|
391
416
|
previous = step.version;
|
|
392
417
|
}
|
|
@@ -394,6 +419,101 @@ export function assertPersistenceMigrationContract(contract) {
|
|
|
394
419
|
throw new Error(`Last migration step version ${previous} must equal targetSchemaVersion ${contract.targetSchemaVersion}`);
|
|
395
420
|
}
|
|
396
421
|
}
|
|
422
|
+
function schemaKey(columns) {
|
|
423
|
+
return columns.join("\u0000");
|
|
424
|
+
}
|
|
425
|
+
function normalizedDefault(value) {
|
|
426
|
+
return value?.trim().toLowerCase().replace(/::[a-z_ ]+$/, "");
|
|
427
|
+
}
|
|
428
|
+
function compatibleColumnType(dialect, actual, expected) {
|
|
429
|
+
const type = actual.trim().toUpperCase();
|
|
430
|
+
if (dialect === "sqlite") {
|
|
431
|
+
if (expected === "integer" || expected === "boolean")
|
|
432
|
+
return type === "INTEGER";
|
|
433
|
+
if (expected === "number")
|
|
434
|
+
return type === "REAL";
|
|
435
|
+
return type === "TEXT";
|
|
436
|
+
}
|
|
437
|
+
if (expected === "integer")
|
|
438
|
+
return type === "INTEGER";
|
|
439
|
+
if (expected === "boolean")
|
|
440
|
+
return type === "BOOLEAN";
|
|
441
|
+
if (expected === "number")
|
|
442
|
+
return type === "DOUBLE PRECISION";
|
|
443
|
+
return type === "TEXT";
|
|
444
|
+
}
|
|
445
|
+
/** Compare bounded dialect catalog output against every required schema-v3 detail. */
|
|
446
|
+
export function assertPersistenceSchemaShape(shape, dialect, model = createPersistenceSchemaModel()) {
|
|
447
|
+
assertPersistenceSchemaModel(model);
|
|
448
|
+
const actualTables = new Map(shape.tables.map((table) => [table.name, table]));
|
|
449
|
+
for (const expected of model.tables) {
|
|
450
|
+
const actual = actualTables.get(expected.name);
|
|
451
|
+
if (!actual)
|
|
452
|
+
throw new Error(`Persistence schema missing table ${expected.name}`);
|
|
453
|
+
if (actual.columns.length !== expected.columns.length)
|
|
454
|
+
throw new Error(`Persistence schema table ${expected.name} has unexpected columns`);
|
|
455
|
+
const actualColumns = new Map(actual.columns.map((column) => [column.name, column]));
|
|
456
|
+
for (const column of expected.columns) {
|
|
457
|
+
const found = actualColumns.get(column.name);
|
|
458
|
+
if (!found)
|
|
459
|
+
throw new Error(`Persistence schema table ${expected.name} missing column ${column.name}`);
|
|
460
|
+
if (!compatibleColumnType(dialect, found.type, column.type)) {
|
|
461
|
+
throw new Error(`Persistence schema column ${expected.name}.${column.name} has incompatible type`);
|
|
462
|
+
}
|
|
463
|
+
if (found.nullable !== (column.nullable === true)) {
|
|
464
|
+
throw new Error(`Persistence schema column ${expected.name}.${column.name} has incompatible nullability`);
|
|
465
|
+
}
|
|
466
|
+
if (normalizedDefault(found.defaultValue) !== normalizedDefault(column.defaultValue)) {
|
|
467
|
+
throw new Error(`Persistence schema column ${expected.name}.${column.name} has incompatible default`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (schemaKey(actual.primaryKey) !== schemaKey(expected.primaryKey)) {
|
|
471
|
+
throw new Error(`Persistence schema table ${expected.name} has incompatible primary key`);
|
|
472
|
+
}
|
|
473
|
+
const unique = new Set(actual.uniqueKeys.map(schemaKey));
|
|
474
|
+
for (const key of expected.uniqueKeys ?? []) {
|
|
475
|
+
if (!unique.has(schemaKey(key)) && schemaKey(actual.primaryKey) !== schemaKey(key)) {
|
|
476
|
+
throw new Error(`Persistence schema table ${expected.name} missing unique key (${key.join(", ")})`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const foreign = new Set(actual.foreignKeys.map((key) => `${schemaKey(key.columns)}>${key.referencesTable}:${schemaKey(key.referencesColumns)}`));
|
|
480
|
+
for (const key of expected.foreignKeys ?? []) {
|
|
481
|
+
if (!foreign.has(`${schemaKey(key.columns)}>${key.referencesTable}:${schemaKey(key.referencesColumns)}`)) {
|
|
482
|
+
throw new Error(`Persistence schema table ${expected.name} missing foreign key (${key.columns.join(", ")})`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const actualIndexes = new Map(shape.indexes.map((index) => [index.name, index]));
|
|
487
|
+
for (const expected of model.indexes) {
|
|
488
|
+
const actual = actualIndexes.get(expected.name);
|
|
489
|
+
if (!actual)
|
|
490
|
+
throw new Error(`Persistence schema missing required index ${expected.name}`);
|
|
491
|
+
if (actual.table !== expected.table || schemaKey(actual.columns) !== schemaKey(expected.columns) || actual.unique !== (expected.unique === true)) {
|
|
492
|
+
throw new Error(`Persistence schema index ${expected.name} has incompatible definition`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/** Reject altered migration history before any new DDL or runtime write. */
|
|
497
|
+
export function assertAppliedPersistenceMigrations(contract, applied) {
|
|
498
|
+
assertPersistenceMigrationContract(contract);
|
|
499
|
+
if (applied.length > contract.steps.length)
|
|
500
|
+
throw new Error("Migration history has unknown rows");
|
|
501
|
+
const legacyChecksums = applied.some((row) => row.checksum === null);
|
|
502
|
+
if (legacyChecksums && (applied.length !== contract.steps.length || !applied.every((row) => row.checksum === null))) {
|
|
503
|
+
throw new Error("Migration history has incomplete legacy checksums");
|
|
504
|
+
}
|
|
505
|
+
for (let index = 0; index < applied.length; index++) {
|
|
506
|
+
const row = applied[index];
|
|
507
|
+
const expected = contract.steps[index];
|
|
508
|
+
if (row.name !== expected.name || row.version !== String(expected.version)) {
|
|
509
|
+
throw new Error(`Migration history row ${index} does not match ${expected.name}`);
|
|
510
|
+
}
|
|
511
|
+
if (!legacyChecksums && row.checksum !== expected.checksum) {
|
|
512
|
+
throw new Error(`Migration history checksum mismatch for ${expected.name}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
return { legacyChecksums };
|
|
516
|
+
}
|
|
397
517
|
/**
|
|
398
518
|
* Assert a dialect-local adapter exposes the canonical table and index names.
|
|
399
519
|
* Adapters pass the table/index names their migration runner created.
|
|
@@ -423,23 +543,17 @@ export function assertParameterizedQuery(sql, boundValues) {
|
|
|
423
543
|
}
|
|
424
544
|
/** Simulate migration up + reopen: applied steps must match the contract in order. */
|
|
425
545
|
export function assertMigrationUpAndReopen(contract, appliedAfterUp, appliedAfterReopen) {
|
|
426
|
-
|
|
427
|
-
if (appliedAfterUp.length !== contract.steps.length) {
|
|
428
|
-
throw new Error("Migration up did not apply every contract step");
|
|
429
|
-
}
|
|
430
|
-
for (let i = 0; i < contract.steps.length; i++) {
|
|
431
|
-
const step = contract.steps[i];
|
|
432
|
-
const row = appliedAfterUp[i];
|
|
433
|
-
if (row.name !== step.name || row.version !== String(step.version)) {
|
|
434
|
-
throw new Error(`Applied migration row ${i} does not match contract step ${step.name}`);
|
|
435
|
-
}
|
|
546
|
+
const first = assertAppliedPersistenceMigrations(contract, appliedAfterUp);
|
|
547
|
+
if (appliedAfterUp.length !== contract.steps.length || first.legacyChecksums) {
|
|
548
|
+
throw new Error("Migration up did not apply every checksummed contract step");
|
|
436
549
|
}
|
|
437
|
-
|
|
438
|
-
|
|
550
|
+
const second = assertAppliedPersistenceMigrations(contract, appliedAfterReopen);
|
|
551
|
+
if (second.legacyChecksums || appliedAfterReopen.length !== appliedAfterUp.length) {
|
|
552
|
+
throw new Error("Reopened adapter migration history diverged");
|
|
439
553
|
}
|
|
440
|
-
for (let
|
|
441
|
-
if (appliedAfterReopen[
|
|
442
|
-
throw new Error("Reopened adapter migration
|
|
554
|
+
for (let index = 0; index < appliedAfterUp.length; index++) {
|
|
555
|
+
if (appliedAfterReopen[index].checksum !== appliedAfterUp[index].checksum) {
|
|
556
|
+
throw new Error("Reopened adapter migration checksums diverged");
|
|
443
557
|
}
|
|
444
558
|
}
|
|
445
559
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { JsonObject, ModelConfig, ProviderRequestOptions } from "./contracts.js";
|
|
2
|
+
/**
|
|
3
|
+
* Portable thinking / reasoning effort levels shared across first-party providers.
|
|
4
|
+
* Model-dependent legality (which values a given model accepts) stays provider-owned.
|
|
5
|
+
*/
|
|
6
|
+
export declare const THINKING_LEVELS: readonly ["none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
7
|
+
export type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
8
|
+
/**
|
|
9
|
+
* Compat mapping families used by ≥2 packages, or explicit no-op for host-owned adapters.
|
|
10
|
+
* Provider packages keep unique escape hatches (budgets, keep/all, tool_stream) local.
|
|
11
|
+
*/
|
|
12
|
+
export type ThinkingCompatFamily = "openai_reasoning" | "reasoning_effort" | "thinking_type" | "noop";
|
|
13
|
+
export declare function isThinkingLevel(value: unknown): value is ThinkingLevel;
|
|
14
|
+
/**
|
|
15
|
+
* Normalize a host thinkingLevel string. Known levels are lowercased; other non-empty
|
|
16
|
+
* strings pass through as opaque effort values for forward-compatible provider fields.
|
|
17
|
+
*/
|
|
18
|
+
export declare function normalizeThinkingLevel(level: string): ThinkingLevel | string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Build the `ProviderRequestOptions.compat` patch for a shared thinking level.
|
|
21
|
+
* Does not invent a second options tree — providers keep reading official fields from `compat`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function thinkingCompatFor(family: ThinkingCompatFamily, level: ThinkingLevel | string): JsonObject;
|
|
24
|
+
/**
|
|
25
|
+
* Merge a shared thinking level into `providerOptions.compat` for the given family.
|
|
26
|
+
* Per-turn patches win over prior compat via {@link mergeProviderRequestOptions}.
|
|
27
|
+
*/
|
|
28
|
+
export declare function applyThinkingLevel(options: ProviderRequestOptions | undefined, level: ThinkingLevel | string, family?: ThinkingCompatFamily): ProviderRequestOptions;
|
|
29
|
+
/**
|
|
30
|
+
* Best-effort family inference from model metadata without a second options tree.
|
|
31
|
+
* Prefer an explicit family in hosts/use-case workers when the provider is known.
|
|
32
|
+
*
|
|
33
|
+
* Heuristics (ordered):
|
|
34
|
+
* 1. Existing `compat.thinking` object → `thinking_type`
|
|
35
|
+
* 2. Existing `compat.reasoning` → `openai_reasoning`
|
|
36
|
+
* 3. Existing `compat.reasoning_effort` → `reasoning_effort`
|
|
37
|
+
* 4. Provider id starting with `openai` → `openai_reasoning`
|
|
38
|
+
* 5. Provider id `neuralwatt` → `reasoning_effort`
|
|
39
|
+
* 6. `capabilities.reasoning` → `reasoning_effort` (portable string field)
|
|
40
|
+
* 7. Else `noop`
|
|
41
|
+
*/
|
|
42
|
+
export declare function thinkingFamilyForModel(model: Pick<ModelConfig, "provider" | "compat" | "capabilities">): ThinkingCompatFamily;
|