@arnilo/prism 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +3 -1
- package/dist/agent-loops.js +45 -8
- package/dist/agent-session/helpers.js +2 -2
- package/dist/cache-helpers.d.ts +11 -0
- package/dist/cache-helpers.js +29 -5
- package/dist/cli-provider-add.js +2 -1
- package/dist/context-budget.js +9 -6
- package/dist/contracts-core/agent.d.ts +2 -0
- package/dist/contracts-core/provider.d.ts +2 -0
- package/dist/event-multiplexer.js +0 -4
- package/dist/index.d.ts +6 -4
- package/dist/index.js +5 -3
- package/dist/input.js +19 -11
- package/dist/node/session-store-jsonl.js +7 -3
- package/dist/providers/openai-compatible.js +2 -1
- package/dist/providers/openai-primitives.js +2 -1
- package/dist/providers/schema.d.ts +7 -0
- package/dist/providers/schema.js +25 -0
- package/dist/testing/provider-conformance.d.ts +10 -0
- package/dist/testing/provider-conformance.js +37 -0
- package/dist/trim-trailing-slashes.d.ts +8 -0
- package/dist/trim-trailing-slashes.js +14 -0
- package/docs/0.1.0-readiness.md +1 -1
- package/docs/acp.md +1 -0
- package/docs/ag-ui.md +1 -0
- package/docs/agent-loops.md +3 -0
- package/docs/agent-session-runtime.md +1 -0
- package/docs/browser-automation.md +1 -0
- package/docs/database-persistence.md +1 -1
- package/docs/graft.md +125 -0
- package/docs/host-security.md +3 -1
- package/docs/index.md +11 -9
- package/docs/input-and-prompt-assembly.md +11 -6
- package/docs/instruction-injection.md +1 -1
- package/docs/mcp-tools.md +1 -0
- package/docs/migration.md +10 -0
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/obscura.md +175 -0
- package/docs/observability.md +21 -1
- package/docs/performance.md +58 -4
- package/docs/ponytail.md +1 -1
- package/docs/provider-caching.md +13 -11
- package/docs/provider-conformance.md +6 -0
- package/docs/provider-packages.md +1 -1
- package/docs/provider-primitives.md +15 -2
- package/docs/providers/ai-sdk.md +1 -1
- package/docs/providers/anthropic.md +1 -1
- package/docs/providers/azure.md +1 -0
- package/docs/providers/bedrock.md +1 -0
- package/docs/providers/kimi.md +2 -1
- package/docs/providers/openai.md +19 -7
- package/docs/providers/opencode-go.md +3 -1
- package/docs/providers/openrouter.md +4 -3
- package/docs/providers/vertex.md +1 -0
- package/docs/public-contracts.md +2 -1
- package/docs/rag.md +55 -8
- package/docs/release-and-install.md +45 -7
- package/docs/server.md +1 -0
- package/docs/supervisors.md +3 -2
- package/docs/system-prompts.md +1 -1
- package/docs/tools.md +1 -1
- package/docs/web-tools.md +2 -0
- package/docs/wiki.md +140 -0
- package/docs/workflows.md +4 -3
- package/docs/working-and-semantic-memory.md +20 -0
- package/package.json +12 -5
- package/docs/api-page-template.md +0 -32
- package/docs/release-0.2.7-evidence.md +0 -514
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
## [0.3.1] - 2026-08-29
|
|
2
|
+
|
|
3
|
+
> Root + 28 changed packages move to 0.3.1 in the plan 039 changed-package cut; `@arnilo/prism-rag` moves 0.3.1 → 0.3.2; `@arnilo/prism-obscura` publishes new at 0.3.0. Distinct from the 2026-08-26 rag/memory/otel 0.3.1 patch below (independent Decision B tags).
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `@arnilo/prism-obscura` (new workspace package, 0.3.0): optional Obscura headless-browser engine over a **host-installed** binary — fail-closed `spawnObscuraProcess` lifecycle (absolute shell-free command, Docker argv, bounded readiness, group close), `createObscuraMcpTools` bridging the complete advertised MCP surface (reads effect-free; mutations and unknown future tools exclusive/serialized; `obscura_` prefix; loopback-default HTTP), `connectObscuraCdp` managed/external CDP + Playwright `connectOverCDP` composition, and `createObscuraWebTools` (standard `web_search`/`web_fetch` through one replaceable HTML search profile plus native `obscura_fetch`/`obscura_scrape`; public-HTTP(S)-only URL validation, byte/count/timeout caps, `allowEval`-gated custom expressions, untrusted-content labeling). Deliberately omitted from `prism-all`/`prism-base`/`prism-sdk` umbrella profiles — installation does not supply the binary/image; consumers install it explicitly.
|
|
7
|
+
- Cross-host conformance (`scripts/obscura-host-conformance.test.mjs`, wired into `npm test`): the same Obscura `ToolDefinition[]` executes through core agent sessions, the Prism MCP server, the server handler, AG-UI, ACP, workflow nodes, supervisor children, and Antigravity delegated exposure — no host branch; host authorization/selection deny before execution; abort kills owned children.
|
|
8
|
+
- New required peers: `@arnilo/prism-obscura` peers `@arnilo/prism` and `@arnilo/prism-web-tools` (reused citation/normalized shapes; `WebProvider` widened with `"obscura"`); `@arnilo/prism-obscura` also peers `@arnilo/prism-mcp`, `@arnilo/prism-browser`, and optional `playwright-core@1.61.0`.
|
|
9
|
+
|
|
10
|
+
## [0.3.1] - 2026-08-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Release 0.3.1 (plan 034)** is the first Decision B independent patch: only `@arnilo/prism-rag`, `@arnilo/prism-memory`, and `@arnilo/prism-observability-opentelemetry` move `0.3.0 → 0.3.1`. Internal `^0.3.0` ranges stay valid; root and every other workspace package stay at their pre-plan versions. Production RAG engine (P1–P8):
|
|
14
|
+
- **P1 transactional pgvector store** — `createPostgresVectorStore` (standalone) plus the existing `createPostgresMemoryStores().vectorStore` now implement `getBySource` + `transaction`. Additive DDL: `embedder_id` / `content_hash` / `generation`, `text_tsv` + GIN, HNSW when the embedding dimension is pinned, per-scope generation pointer table. Host names schema/table and owns the knowledge database.
|
|
15
|
+
- **P2 hybrid retrieve** — `retrieveContext({ lexical: "fts" | "bm25" | "off" })` fuses vector + lexical legs with RRF (`fusion: "rrf"`, `rrfK` 60 / cap 1,000). Provenance `retrieval` is `vector` | `lexical` | `hybrid`. Stores advertise `lexicalModes`; unsupported `bm25` fails closed. `fuseReciprocalRank` / `tokenizeLexical` exported.
|
|
16
|
+
- **P3 embedder identity** — `Embedder.id` is required and stamped as `embedderId`. Retrieve fails closed with `ERR_PRISM_RAG_EMBEDDER_MISMATCH` on id/dimension drift or legacy rows without an id (re-index path named). **TypeScript break for Embedder implementers** — add `readonly id: string`; see `docs/migration.md` `0.3.0 → 0.3.1`.
|
|
17
|
+
- **P4 content-hash skip** — `replaceSource({ contentHash })` skips unchanged sources (`{ indexed: 0, skipped: true }`) and reuses embeddings when chunk text matches (`reuseEmbeddings`, `isValidContentHash`).
|
|
18
|
+
- **P5 heading metadata** — `chunkMarkdown` stamps `metadata.heading` (parent-first ATX stack) unless the caller supplied one; parser metadata (`page`, `section`, …) copies onto chunks.
|
|
19
|
+
- **P6 generation visibility** — `replaceSource` bumps a scope-level generation; retrieval filters to the current generation (legacy generation-less rows stay visible). `getCurrentGeneration` / `setCurrentGeneration` support rollback.
|
|
20
|
+
- **P7 RAG telemetry** — dependency-free `RagTelemetry` seam; `createRagTelemetry()` in the OpenTelemetry package maps the `rag_request` / `rag_index` span tree. Span-name + `rag.*` allow-list drops raw chunk text.
|
|
21
|
+
- **P8 TEI reranker** — `createTeiReranker({ baseUrl })` posts `{query, texts, raw_scores:false}` to host TEI `/rerank`, permutation-only, fail-closed parse, 65,536-byte ceiling, `pinnedFetch` default. SSRF/URL policy stays host-side.
|
|
22
|
+
- **multi-scope retrieve** — `retrieveContext({ scopes: [org, user, session] })` embeds once, runs per-scope vector/lexical legs, fuses with one RRF, reranks once, and cuts `topK`. `scope` still valid; empty `scopes` returns zero hits with no embed/search/rerank (host “no allowed corpora” path). `HARD_RETRIEVE_SCOPE_CAP` is 8. Provenance carries `tenantId`/`resourceId`/`corpusId`. Telemetry: `rag.scope_count` + chunk tenant/corpus.
|
|
23
|
+
- Compat baselines regenerated (`--update-baseline`). Scanner sees additive exports only; the Embedder.id implementer break is documented, not a name-level removal. No `--allow-break`. Publication remains the operator handoff (`docs/release-and-install.md` `0.3.1 independent RAG engine patch` — package tags `@arnilo/prism-memory@0.3.1`, `@arnilo/prism-rag@0.3.1`, `@arnilo/prism-observability-opentelemetry@0.3.1`).
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Store compatibility with 0.3.0: **compatible, additive**. New columns/indexes/tables are `IF NOT EXISTS`; 0.3.0 rows remain readable. Rollback = restore the 0.3.0 package versions (generation-less retrieve still works; heading/hash/telemetry/TEI simply disappear). No root/core persisted-shape change.
|
|
27
|
+
|
|
1
28
|
## [0.3.0] - 2026-08-20
|
|
2
29
|
|
|
3
30
|
### Added
|
package/README.md
CHANGED
|
@@ -171,8 +171,10 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
171
171
|
| `@arnilo/prism-provider-vertex` | Google Vertex provider |
|
|
172
172
|
| `@arnilo/prism-provider-ai-sdk` | AI SDK interoperability adapter |
|
|
173
173
|
| `@arnilo/prism-browser` | optional host-wired Playwright browser automation (not core; not auto-activated) |
|
|
174
|
+
| `@arnilo/prism-obscura` | optional Obscura headless-browser engine over a host-installed binary: MCP surface, CDP + Playwright, bounded CLI web tools (not in umbrellas; install explicitly) |
|
|
174
175
|
| `@arnilo/prism-computer-use-linux` | optional Linux desktop-control tools over a host-owned `computer-use-linux` MCP binary |
|
|
175
176
|
| `@arnilo/prism-antigravity-agent` | optional Antigravity CLI delegated agent adapter with per-run Prism MCP capability exposure |
|
|
177
|
+
| `@arnilo/prism-wiki` | optional Karpathy LLM Wiki knowledge compiler with local `qmd` hybrid search and Context7 line navigation |
|
|
176
178
|
| `@arnilo/prism-compaction-llm` | provider-backed compaction strategy |
|
|
177
179
|
| `@arnilo/prism-compaction-observational-memory` | source-backed memory + recall tool |
|
|
178
180
|
| `@arnilo/prism-coding-agent` | bounded shell/read/write/edit tools |
|
|
@@ -191,7 +193,7 @@ printf '{"id":"1","command":"prompt","params":{"input":"Hi"}}\n' \
|
|
|
191
193
|
| `@arnilo/prism-base` | profile: core + compaction + JSON Schema validation |
|
|
192
194
|
| `@arnilo/prism-code` | profile: base + coding tools/security + MCP |
|
|
193
195
|
| `@arnilo/prism-sdk` | profile: base + workflows + MCP + credentials + OpenTelemetry |
|
|
194
|
-
| `@arnilo/prism-all` | broad umbrella: 21 first-party packages (47 transitive) across a 47-package workspace closure — omits document-reader, OpenAPI tools, NATS, Caveman, Ponytail, Impeccable, computer-use-linux,
|
|
196
|
+
| `@arnilo/prism-all` | broad umbrella: 21 first-party packages (47 transitive) across a 47-package workspace closure — omits document-reader, OpenAPI tools, NATS, Caveman, Ponytail, Impeccable, computer-use-linux, antigravity-agent, Graft, Obscura, and wiki |
|
|
195
197
|
|
|
196
198
|
## Scripts
|
|
197
199
|
|
package/dist/agent-loops.js
CHANGED
|
@@ -80,12 +80,29 @@ export function generateValidateReviseLoop(opts) {
|
|
|
80
80
|
const max = opts.maxRevisions ?? 3;
|
|
81
81
|
const repairer = opts.repairer ?? defaultRepairer();
|
|
82
82
|
const finalOnly = opts.structuredOutputTiming === "final-turn-only" && opts.toolCalls === "bounded";
|
|
83
|
-
// ponytail:
|
|
84
|
-
//
|
|
83
|
+
// ponytail: keep only current-run state in this strategy so snapshot/restore can capture it;
|
|
84
|
+
// supplied strategy instances may be reused, so a new non-restored run resets these locals.
|
|
85
85
|
let attempts = 0;
|
|
86
86
|
let artifactPhase = !finalOnly;
|
|
87
87
|
let savedSchema;
|
|
88
88
|
let pendingHistory = [];
|
|
89
|
+
let stateOwner;
|
|
90
|
+
let restoredStatePending = false;
|
|
91
|
+
const resetState = () => {
|
|
92
|
+
attempts = 0;
|
|
93
|
+
artifactPhase = !finalOnly;
|
|
94
|
+
savedSchema = undefined;
|
|
95
|
+
pendingHistory = [];
|
|
96
|
+
};
|
|
97
|
+
const prepareRunState = (ctx) => {
|
|
98
|
+
const sameRun = stateOwner?.sessionId === ctx.sessionId && stateOwner.runId === ctx.runId;
|
|
99
|
+
if (!sameRun) {
|
|
100
|
+
if (!restoredStatePending)
|
|
101
|
+
resetState();
|
|
102
|
+
stateOwner = { sessionId: ctx.sessionId, runId: ctx.runId };
|
|
103
|
+
}
|
|
104
|
+
restoredStatePending = false;
|
|
105
|
+
};
|
|
89
106
|
return {
|
|
90
107
|
name: "generate-validate-revise",
|
|
91
108
|
revision: "1",
|
|
@@ -108,8 +125,10 @@ export function generateValidateReviseLoop(opts) {
|
|
|
108
125
|
// Repair messages were appended to the session before suspension, so the rebuilt
|
|
109
126
|
// history already carries them; re-applying pendingHistory would duplicate them.
|
|
110
127
|
pendingHistory = [];
|
|
128
|
+
restoredStatePending = true;
|
|
111
129
|
},
|
|
112
130
|
async run(ctx) {
|
|
131
|
+
prepareRunState(ctx);
|
|
113
132
|
let usage;
|
|
114
133
|
let nextInput = ctx.input;
|
|
115
134
|
let toolRounds = 0;
|
|
@@ -260,17 +279,35 @@ export async function dispatchToolCallsInOrder(calls, ctx) {
|
|
|
260
279
|
}
|
|
261
280
|
const results = new Array(calls.length);
|
|
262
281
|
let nextIndex = 0;
|
|
282
|
+
let stopped = false;
|
|
283
|
+
let firstFailure;
|
|
284
|
+
const recordFailure = (error) => {
|
|
285
|
+
if (stopped)
|
|
286
|
+
return;
|
|
287
|
+
stopped = true;
|
|
288
|
+
firstFailure = error;
|
|
289
|
+
};
|
|
263
290
|
const workers = Array.from({ length: concurrency }, async () => {
|
|
264
291
|
for (;;) {
|
|
265
|
-
|
|
266
|
-
const index = nextIndex;
|
|
267
|
-
nextIndex += 1;
|
|
268
|
-
if (index >= calls.length)
|
|
292
|
+
if (stopped)
|
|
269
293
|
return;
|
|
270
|
-
|
|
294
|
+
try {
|
|
295
|
+
throwIfAborted(ctx.signal);
|
|
296
|
+
const index = nextIndex;
|
|
297
|
+
nextIndex += 1;
|
|
298
|
+
if (index >= calls.length)
|
|
299
|
+
return;
|
|
300
|
+
results[index] = await ctx.dispatchToolCall(calls[index]);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
recordFailure(error);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
271
306
|
}
|
|
272
307
|
});
|
|
273
|
-
await Promise.
|
|
308
|
+
await Promise.allSettled(workers);
|
|
309
|
+
if (stopped)
|
|
310
|
+
throw firstFailure;
|
|
274
311
|
for (const result of results) {
|
|
275
312
|
throwIfAborted(ctx.signal);
|
|
276
313
|
if (!result)
|
|
@@ -142,10 +142,10 @@ export function throwIfAbortedSignal(signal) {
|
|
|
142
142
|
if (signal?.aborted)
|
|
143
143
|
throw signal.reason instanceof Error ? signal.reason : new Error("Agent run aborted");
|
|
144
144
|
}
|
|
145
|
-
const jsonTextEncoder = new TextEncoder();
|
|
146
145
|
export function jsonBytes(value) {
|
|
147
146
|
try {
|
|
148
|
-
|
|
147
|
+
const serialized = JSON.stringify(value);
|
|
148
|
+
return serialized === undefined ? 0 : Buffer.byteLength(serialized, "utf8");
|
|
149
149
|
}
|
|
150
150
|
catch {
|
|
151
151
|
throw new TypeError("Provider request or event must be JSON-serializable for run limits");
|
package/dist/cache-helpers.d.ts
CHANGED
|
@@ -23,6 +23,17 @@ export interface CacheUsageReport {
|
|
|
23
23
|
export declare function sanitizeCacheKey(value: string | undefined, maxLength: number): string | undefined;
|
|
24
24
|
export declare function mapCacheRetention(retention: CacheRetention | undefined, model: ModelConfig): "short" | "long" | undefined;
|
|
25
25
|
export declare function applyCacheControl(messages: readonly Message[], breakpoints: readonly PromptCacheBreakpoint[], options?: ApplyCacheControlOptions): readonly CacheControlledMessage[];
|
|
26
|
+
export interface SystemCacheControlTextBlock {
|
|
27
|
+
readonly type: "text";
|
|
28
|
+
readonly text: string;
|
|
29
|
+
readonly cache_control?: CacheControlValue;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Provider-style `system` field: plain joined string by default; native text
|
|
33
|
+
* blocks only when cache-control markers must survive (system_prompt breakpoints).
|
|
34
|
+
*/
|
|
35
|
+
export declare function systemCacheControlField(messages: readonly CacheControlledMessage[], toText: (message: Message) => string): string | readonly SystemCacheControlTextBlock[] | undefined;
|
|
26
36
|
export declare function cacheHitRate(usage: Usage | undefined): number | undefined;
|
|
27
37
|
export declare function cacheSavings(usage: Usage | undefined, model: ModelConfig): number | undefined;
|
|
28
38
|
export declare function cacheUsageReport(usage: Usage | undefined, model?: ModelConfig): CacheUsageReport | undefined;
|
|
39
|
+
export declare function resolveBreakpoint(messages: readonly Message[], breakpoint: PromptCacheBreakpoint): number;
|
package/dist/cache-helpers.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
export function sanitizeCacheKey(value, maxLength) {
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const replaced = value?.replace(/[^A-Za-z0-9_.:-]+/g, "-");
|
|
3
|
+
if (!replaced)
|
|
4
|
+
return undefined;
|
|
5
|
+
// Index/slice edge trim instead of the `/^-+|-+$/g` alternation (CodeQL js/polynomial-redos, alert 22).
|
|
6
|
+
let start = 0;
|
|
7
|
+
let end = replaced.length;
|
|
8
|
+
while (start < end && replaced.charCodeAt(start) === 45)
|
|
9
|
+
start += 1;
|
|
10
|
+
while (end > start && replaced.charCodeAt(end - 1) === 45)
|
|
11
|
+
end -= 1;
|
|
12
|
+
const key = replaced.slice(start, end).slice(0, Math.max(0, maxLength));
|
|
6
13
|
return key || undefined;
|
|
7
14
|
}
|
|
8
15
|
export function mapCacheRetention(retention, model) {
|
|
@@ -29,6 +36,23 @@ export function applyCacheControl(messages, breakpoints, options = {}) {
|
|
|
29
36
|
return { ...message, content };
|
|
30
37
|
});
|
|
31
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Provider-style `system` field: plain joined string by default; native text
|
|
41
|
+
* blocks only when cache-control markers must survive (system_prompt breakpoints).
|
|
42
|
+
*/
|
|
43
|
+
export function systemCacheControlField(messages, toText) {
|
|
44
|
+
const system = messages.filter((message) => message.role === "system");
|
|
45
|
+
if (!system.length)
|
|
46
|
+
return undefined;
|
|
47
|
+
if (!system.some((message) => message.content.some((block) => block.cache_control))) {
|
|
48
|
+
return system.map(toText).join("\n\n") || undefined;
|
|
49
|
+
}
|
|
50
|
+
return system.map((message) => {
|
|
51
|
+
const cache_control = message.content.find((block) => block.cache_control)?.cache_control;
|
|
52
|
+
const block = { type: "text", text: toText(message) };
|
|
53
|
+
return cache_control ? { ...block, cache_control } : block;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
32
56
|
export function cacheHitRate(usage) {
|
|
33
57
|
const read = usage?.cacheReadTokens;
|
|
34
58
|
const input = usage?.inputTokens;
|
|
@@ -54,7 +78,7 @@ export function cacheUsageReport(usage, model) {
|
|
|
54
78
|
currency: estimatedSavings === undefined ? usage.currency : (model?.cost?.currency ?? usage.currency),
|
|
55
79
|
};
|
|
56
80
|
}
|
|
57
|
-
function resolveBreakpoint(messages, breakpoint) {
|
|
81
|
+
export function resolveBreakpoint(messages, breakpoint) {
|
|
58
82
|
switch (breakpoint.location) {
|
|
59
83
|
case "system_prompt":
|
|
60
84
|
return messages.findIndex((message) => message.role === "system");
|
package/dist/cli-provider-add.js
CHANGED
|
@@ -2,6 +2,7 @@ import { accessSync, constants as fsConstants } from "node:fs";
|
|
|
2
2
|
import { access, mkdir, readdir, readFile, realpath, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { trimTrailingSlashes } from "./trim-trailing-slashes.js";
|
|
5
6
|
export class ProviderAddUsageError extends Error {
|
|
6
7
|
}
|
|
7
8
|
const NPM_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
@@ -208,7 +209,7 @@ function buildTokens(input) {
|
|
|
208
209
|
__PROVIDER_PASCAL__: pascal,
|
|
209
210
|
__PACKAGE_NAME__: input.name,
|
|
210
211
|
__PRISM_VERSION__: input.version,
|
|
211
|
-
__BASE_URL__: input.baseUrl
|
|
212
|
+
__BASE_URL__: trimTrailingSlashes(input.baseUrl),
|
|
212
213
|
__ENV_KEY__: input.envKey,
|
|
213
214
|
__MODEL_ID__: input.model,
|
|
214
215
|
};
|
package/dist/context-budget.js
CHANGED
|
@@ -20,7 +20,7 @@ export function estimateTextTokens(text) {
|
|
|
20
20
|
return Math.ceil(text.length / 4);
|
|
21
21
|
}
|
|
22
22
|
export function estimateTextBytes(text) {
|
|
23
|
-
return
|
|
23
|
+
return Buffer.byteLength(text, "utf8");
|
|
24
24
|
}
|
|
25
25
|
export function estimateMessageTokens(message) {
|
|
26
26
|
return estimateTextTokens(messageText(message));
|
|
@@ -64,11 +64,12 @@ export function applyContextBudget(options) {
|
|
|
64
64
|
const tools = options.tools ? [...options.tools] : undefined;
|
|
65
65
|
const omitted = [];
|
|
66
66
|
const demotedBodies = new Set();
|
|
67
|
+
const historyCursor = { index: 0 };
|
|
67
68
|
// Measure once, then subtract each dropped item's own estimate (dropNext computes it
|
|
68
69
|
// with the same estimators) — avoids an O(n²) re-scan of the full keep-set per drop.
|
|
69
70
|
const kept = measureAll(groups, context, skills, tools, skillContext, demotedBodies);
|
|
70
71
|
while (overBudget(kept, budget)) {
|
|
71
|
-
const drop = dropNext(groups, context, skills, layout, skillContext, demotedBodies);
|
|
72
|
+
const drop = dropNext(groups, context, skills, layout, skillContext, demotedBodies, historyCursor);
|
|
72
73
|
if (!drop) {
|
|
73
74
|
throw new ContextBudgetError();
|
|
74
75
|
}
|
|
@@ -77,6 +78,8 @@ export function applyContextBudget(options) {
|
|
|
77
78
|
if (omitted.length < HARD_MAX_CONTEXT_BUDGET_OMISSIONS)
|
|
78
79
|
omitted.push(drop);
|
|
79
80
|
}
|
|
81
|
+
if (historyCursor.index > 0)
|
|
82
|
+
groups.history = groups.history.slice(historyCursor.index);
|
|
80
83
|
const reportOmissions = omitted.slice(0, DEFAULT_MAX_CONTEXT_BUDGET_OMISSIONS);
|
|
81
84
|
return {
|
|
82
85
|
groups,
|
|
@@ -94,8 +97,8 @@ export function applyContextBudget(options) {
|
|
|
94
97
|
},
|
|
95
98
|
};
|
|
96
99
|
}
|
|
97
|
-
function dropNext(groups, context, skills, layout, skillContext, demotedBodies) {
|
|
98
|
-
// ponytail: drop droppable groups in layout order; within history,
|
|
100
|
+
function dropNext(groups, context, skills, layout, skillContext, demotedBodies, historyCursor) {
|
|
101
|
+
// ponytail: drop droppable groups in layout order; within history, advance a cursor and slice once.
|
|
99
102
|
// cache_aware keeps attachments longer so stable prefix stays intact while budget still allows it.
|
|
100
103
|
const order = layout === "cache_aware"
|
|
101
104
|
? ["tool_results", "history", "summaries", "context", "skills", "attachments"]
|
|
@@ -105,8 +108,8 @@ function dropNext(groups, context, skills, layout, skillContext, demotedBodies)
|
|
|
105
108
|
const message = groups.toolResults.pop();
|
|
106
109
|
return omission("tool_results", message.id ?? toolResultId(message), message);
|
|
107
110
|
}
|
|
108
|
-
if (kind === "history" && groups.history.length
|
|
109
|
-
const message = groups.history.
|
|
111
|
+
if (kind === "history" && historyCursor.index < groups.history.length) {
|
|
112
|
+
const message = groups.history[historyCursor.index++];
|
|
110
113
|
return omission("history", message.id, message);
|
|
111
114
|
}
|
|
112
115
|
if (kind === "summaries" && groups.summaries.length > 0) {
|
|
@@ -225,6 +225,8 @@ export interface PromptBuilder {
|
|
|
225
225
|
}
|
|
226
226
|
export interface PromptBuildRequest {
|
|
227
227
|
readonly messages: readonly Message[];
|
|
228
|
+
/** Input layout selected by the host; the default builder uses cache-aware ordering unless legacy is explicit. */
|
|
229
|
+
readonly inputLayout?: InputAssemblyLayout;
|
|
228
230
|
readonly context?: readonly ContextBlock[];
|
|
229
231
|
readonly skills?: readonly Skill[];
|
|
230
232
|
readonly skillsDisclosure?: import("../skill-disclosure.js").SkillsDisclosure;
|
|
@@ -11,6 +11,8 @@ export interface ModelCacheCapabilities {
|
|
|
11
11
|
readonly maxBreakpoints?: number;
|
|
12
12
|
readonly minCacheableTokens?: number;
|
|
13
13
|
readonly longRetention?: boolean;
|
|
14
|
+
/** Model accepts explicit per-block cache breakpoints (e.g. OpenAI GPT-5.6+ `prompt_cache_breakpoint`). */
|
|
15
|
+
readonly explicitBreakpoints?: boolean;
|
|
14
16
|
}
|
|
15
17
|
export type PromptCacheMode = "auto" | "on" | "off";
|
|
16
18
|
export type PromptCacheBreakpointLocation = "system_prompt" | "tools" | "stable_context" | "last_stable_message" | "last_user_message" | "message_id";
|
|
@@ -119,10 +119,6 @@ export function createEventMultiplexer(options = {}) {
|
|
|
119
119
|
isClosed = true;
|
|
120
120
|
options.signal?.removeEventListener("abort", abort);
|
|
121
121
|
finishSources();
|
|
122
|
-
queue.length = 0;
|
|
123
|
-
// Free the single-consumer slot: a consumer suspended at a yield may never pull
|
|
124
|
-
// again, and after close a fresh subscriber terminates immediately anyway.
|
|
125
|
-
activeConsumer = false;
|
|
126
122
|
if (waiter) {
|
|
127
123
|
const resolve = waiter;
|
|
128
124
|
waiter = undefined;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export { createAgent, createAgentSession, resumeAgentRun, resumeAgentRunStream }
|
|
|
10
10
|
export type { ArtifactApproval, ArtifactApprovalState, ArtifactBodyErrorCode, ArtifactBodyPresignOptions, ArtifactBodyRef, ArtifactBodyStore, ArtifactBodyTransferOptions, ArtifactCitation, ArtifactDecisionState, ArtifactDeliveryToken, ArtifactRecord, ArtifactRevision, } from "./artifacts.js";
|
|
11
11
|
export { ARTIFACT_BODY_ERROR_CODES, ARTIFACT_CHECKPOINT_NAMESPACE, ArtifactBodyStoreError, ArtifactError, artifactApprovalState, artifactCheckpointKey, } from "./artifacts.js";
|
|
12
12
|
export type { ApplyCacheControlOptions, CacheControlledContentBlock, CacheControlledMessage, CacheControlValue, CacheUsageReport, } from "./cache-helpers.js";
|
|
13
|
-
export { applyCacheControl, cacheHitRate, cacheSavings, cacheUsageReport, mapCacheRetention, sanitizeCacheKey } from "./cache-helpers.js";
|
|
13
|
+
export { applyCacheControl, cacheHitRate, cacheSavings, cacheUsageReport, mapCacheRetention, resolveBreakpoint, sanitizeCacheKey, systemCacheControlField, } from "./cache-helpers.js";
|
|
14
14
|
export type { CacheTelemetry, CacheTelemetryOptions, CacheTelemetryReport, CacheTelemetrySample, } from "./cache-telemetry.js";
|
|
15
15
|
export { CACHE_TELEMETRY_OVERFLOW_KEY, CacheTelemetryError, createCacheTelemetry, DEFAULT_CACHE_TELEMETRY_CAP, } from "./cache-telemetry.js";
|
|
16
16
|
export type { MemoryCheckpointStoreOptions } from "./checkpoints.js";
|
|
@@ -33,12 +33,12 @@ export type { ConversationBranchRef, ConversationReplayCursor, ConversationThrea
|
|
|
33
33
|
export { CONVERSATION_METADATA_KEY, ConversationError, conversationMarkerMetadata, conversationThreadFromRecord, DEFAULT_MAX_CONVERSATION_CURSOR_BYTES, decodeConversationReplayCursor, encodeConversationReplayCursor, HARD_MAX_CONVERSATION_CURSOR_BYTES, } from "./conversations.js";
|
|
34
34
|
export type { CredentialRecord, CredentialValueSource, MemoryCredentialStore, RevocableOAuthCredentialStore } from "./credentials.js";
|
|
35
35
|
export { createChainedCredentialResolver, createEnvCredentialResolver, createExplicitCredentialResolver, createMemoryCredentialStore, refreshOAuthCredential, resolveCredentialValue, revokeOAuthCredential, } from "./credentials.js";
|
|
36
|
+
export type { DelegatedAgentStepInput } from "./delegated-agent-step.js";
|
|
37
|
+
export { createDelegatedAgentStep, DelegatedAgentStepError, MAX_DELEGATED_AGENT_DURATION_MS, MAX_DELEGATED_AGENT_EVENT_BYTES, MAX_DELEGATED_AGENT_ID_BYTES, MAX_DELEGATED_AGENT_NAME_BYTES, MAX_DELEGATED_AGENT_STEP_INDEX, MAX_DELEGATED_AGENT_TOKEN_COUNT, } from "./delegated-agent-step.js";
|
|
36
38
|
export type { DeviceAdapter, DeviceAdmitRequest, DeviceChunkResult, DeviceConformanceResult, DeviceKind, DevicePolicyErrorCode, DevicePolicyOptions, DeviceStreamLimits, ResolvedDevicePolicy, } from "./devices.js";
|
|
37
39
|
export { acceptDeviceChunk, assertDeviceAdmit, DEFAULT_DEVICE_MAX_CHUNK_BYTES, DEFAULT_DEVICE_MAX_CONCURRENT_SESSIONS, DevicePolicyError, HARD_DEVICE_MAX_CHUNK_BYTES, HARD_DEVICE_MAX_CONCURRENT_SESSIONS, redactDeviceTelemetry, resolveDevicePolicy, runDevicePolicyConformance, } from "./devices.js";
|
|
38
40
|
export type { EventMultiplexer, EventMultiplexerOptions, EventOverflowInfo, EventOverflowPolicy } from "./event-multiplexer.js";
|
|
39
41
|
export { createEventMultiplexer, EVENT_MULTIPLEXER_SINGLE_CONSUMER_CODE, EventMultiplexerError } from "./event-multiplexer.js";
|
|
40
|
-
export { createDelegatedAgentStep, DelegatedAgentStepError, MAX_DELEGATED_AGENT_DURATION_MS, MAX_DELEGATED_AGENT_EVENT_BYTES, MAX_DELEGATED_AGENT_ID_BYTES, MAX_DELEGATED_AGENT_NAME_BYTES, MAX_DELEGATED_AGENT_STEP_INDEX, MAX_DELEGATED_AGENT_TOKEN_COUNT, } from "./delegated-agent-step.js";
|
|
41
|
-
export type { DelegatedAgentStepInput } from "./delegated-agent-step.js";
|
|
42
42
|
export type { ExecutionAction, ExecutionDecision, ExecutionPolicy, ExecutionRisk } from "./execution-policy.js";
|
|
43
43
|
export { applyExecutionDecision, assertExecutionAllowed, checkExecution, ExecutionDeniedError } from "./execution-policy.js";
|
|
44
44
|
export type { ExtensionErrorPolicy, ExtensionEventBus, ExtensionEventHandler, ExtensionKernel, ExtensionKernelOptions, ExtensionLoadPolicy, LoadedExtension, } from "./extensions.js";
|
|
@@ -73,6 +73,7 @@ export { providerContentDelta, providerContinuationRequired, providerDone, provi
|
|
|
73
73
|
export { authMethodKey, defineProviderPackage, systemPromptContributionKey } from "./provider-packages.js";
|
|
74
74
|
export type { SessionCachePolicyOptions } from "./provider-request-policy.js";
|
|
75
75
|
export { createProviderRequestPolicyChain, createSessionCachePolicy, mergeProviderRequestOptions } from "./provider-request-policy.js";
|
|
76
|
+
export { canonicalizeJsonSchema } from "./providers/schema.js";
|
|
76
77
|
export type { ProviderRegistry, ProviderRegistryOptions } from "./providers.js";
|
|
77
78
|
export { createProviderRegistry, createProviderResolver } from "./providers.js";
|
|
78
79
|
export type { SecretRedactor } from "./redaction.js";
|
|
@@ -110,8 +111,9 @@ export type { FoldToolResultsContext, ResolvedToolResultFoldOptions, ToolResultF
|
|
|
110
111
|
export { DEFAULT_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, DEFAULT_TOOL_RESULT_FOLD_MIN_AGE_TURNS, DEFAULT_TOOL_RESULT_FOLD_MIN_BYTES, foldedToolResultHeader, foldToolResultHistory, foldToolResults, formatFoldedToolResult, HARD_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, resolveToolResultFold, TOOL_RESULT_FOLD_TURN_METADATA_KEY, } from "./tool-result-fold.js";
|
|
111
112
|
export type { DispatchToolCallOptions, ToolArgumentValidationError, ToolArgumentValidationResult, ToolArgumentValidator, ToolFilter, ToolFilterInput, ToolParameterValidatorOptions, ToolRegistryOptions, ToolValidator, } from "./tools.js";
|
|
112
113
|
export { createToolParameterValidator, createToolRegistry, dispatchToolCall, filterTools } from "./tools.js";
|
|
114
|
+
export { trimTrailingSlashes } from "./trim-trailing-slashes.js";
|
|
113
115
|
export type { ResolvedUseCaseModel, ResolveUseCaseModelInput, UseCaseModelBinding, } from "./use-case-model.js";
|
|
114
116
|
export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
|
|
115
117
|
export declare const name = "prism";
|
|
116
|
-
export declare const version = "0.3.
|
|
118
|
+
export declare const version = "0.3.1";
|
|
117
119
|
export declare const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ export { createAgentRunLifecycle } from "./agent-run-lifecycle.js";
|
|
|
5
5
|
export { AGENT_RUN_STATE_NAMESPACE, AGENT_RUN_STATE_SCHEMA_VERSION, agentFingerprint, DEFAULT_MAX_AGENT_RUN_STATE_BYTES, HARD_MAX_AGENT_RUN_STATE_BYTES, loadAgentRunState, } from "./agent-run-state.js";
|
|
6
6
|
export { createAgent, createAgentSession, resumeAgentRun, resumeAgentRunStream } from "./agents.js";
|
|
7
7
|
export { ARTIFACT_BODY_ERROR_CODES, ARTIFACT_CHECKPOINT_NAMESPACE, ArtifactBodyStoreError, ArtifactError, artifactApprovalState, artifactCheckpointKey, } from "./artifacts.js";
|
|
8
|
-
export { applyCacheControl, cacheHitRate, cacheSavings, cacheUsageReport, mapCacheRetention, sanitizeCacheKey } from "./cache-helpers.js";
|
|
8
|
+
export { applyCacheControl, cacheHitRate, cacheSavings, cacheUsageReport, mapCacheRetention, resolveBreakpoint, sanitizeCacheKey, systemCacheControlField, } from "./cache-helpers.js";
|
|
9
9
|
export { CACHE_TELEMETRY_OVERFLOW_KEY, CacheTelemetryError, createCacheTelemetry, DEFAULT_CACHE_TELEMETRY_CAP, } from "./cache-telemetry.js";
|
|
10
10
|
export { CHECKPOINT_CONFLICT_CODE, CheckpointConflictError, createMemoryCheckpointStore } from "./checkpoints.js";
|
|
11
11
|
export { createDefaultCompactionStrategy, isCompactionEntryData } from "./compaction.js";
|
|
@@ -17,9 +17,9 @@ export { parseAgentFile, parseSkillFile } from "./contribution-parsing.js";
|
|
|
17
17
|
export { createContributionRegistries, createContributionRegistry, registerDiscoveredContributions } from "./contributions.js";
|
|
18
18
|
export { CONVERSATION_METADATA_KEY, ConversationError, conversationMarkerMetadata, conversationThreadFromRecord, DEFAULT_MAX_CONVERSATION_CURSOR_BYTES, decodeConversationReplayCursor, encodeConversationReplayCursor, HARD_MAX_CONVERSATION_CURSOR_BYTES, } from "./conversations.js";
|
|
19
19
|
export { createChainedCredentialResolver, createEnvCredentialResolver, createExplicitCredentialResolver, createMemoryCredentialStore, refreshOAuthCredential, resolveCredentialValue, revokeOAuthCredential, } from "./credentials.js";
|
|
20
|
+
export { createDelegatedAgentStep, DelegatedAgentStepError, MAX_DELEGATED_AGENT_DURATION_MS, MAX_DELEGATED_AGENT_EVENT_BYTES, MAX_DELEGATED_AGENT_ID_BYTES, MAX_DELEGATED_AGENT_NAME_BYTES, MAX_DELEGATED_AGENT_STEP_INDEX, MAX_DELEGATED_AGENT_TOKEN_COUNT, } from "./delegated-agent-step.js";
|
|
20
21
|
export { acceptDeviceChunk, assertDeviceAdmit, DEFAULT_DEVICE_MAX_CHUNK_BYTES, DEFAULT_DEVICE_MAX_CONCURRENT_SESSIONS, DevicePolicyError, HARD_DEVICE_MAX_CHUNK_BYTES, HARD_DEVICE_MAX_CONCURRENT_SESSIONS, redactDeviceTelemetry, resolveDevicePolicy, runDevicePolicyConformance, } from "./devices.js";
|
|
21
22
|
export { createEventMultiplexer, EVENT_MULTIPLEXER_SINGLE_CONSUMER_CODE, EventMultiplexerError } from "./event-multiplexer.js";
|
|
22
|
-
export { createDelegatedAgentStep, DelegatedAgentStepError, MAX_DELEGATED_AGENT_DURATION_MS, MAX_DELEGATED_AGENT_EVENT_BYTES, MAX_DELEGATED_AGENT_ID_BYTES, MAX_DELEGATED_AGENT_NAME_BYTES, MAX_DELEGATED_AGENT_STEP_INDEX, MAX_DELEGATED_AGENT_TOKEN_COUNT, } from "./delegated-agent-step.js";
|
|
23
23
|
export { applyExecutionDecision, assertExecutionAllowed, checkExecution, ExecutionDeniedError } from "./execution-policy.js";
|
|
24
24
|
export { createExtensionEventBus, createExtensionKernel } from "./extensions.js";
|
|
25
25
|
export { createMemoryRunFeedbackStore, prepareRunFeedback, RunFeedbackError, requireRunFeedbackOwnership, runFeedbackPageLimit, } from "./feedback.js";
|
|
@@ -40,6 +40,7 @@ export { boundResponse, defaultResolver, isLoopbackAddress, isLoopbackHostname,
|
|
|
40
40
|
export { providerContentDelta, providerContinuationRequired, providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, toolCallContent, toolCallFromArgumentsText, } from "./provider-events.js";
|
|
41
41
|
export { authMethodKey, defineProviderPackage, systemPromptContributionKey } from "./provider-packages.js";
|
|
42
42
|
export { createProviderRequestPolicyChain, createSessionCachePolicy, mergeProviderRequestOptions } from "./provider-request-policy.js";
|
|
43
|
+
export { canonicalizeJsonSchema } from "./providers/schema.js";
|
|
43
44
|
export { createProviderRegistry, createProviderResolver } from "./providers.js";
|
|
44
45
|
export { createSecretRedactor, errorToErrorInfo, redactAgentEvent, redactMessage, redactProviderRequest, redactRunLedgerRecord, redactSecrets, redactSessionEntry, resolveRedactor, } from "./redaction.js";
|
|
45
46
|
export { loadBinaryResource, loadJsonResource, loadManifestResource, loadTextResource } from "./resources.js";
|
|
@@ -60,8 +61,9 @@ export { applyThinkingLevel, isThinkingLevel, normalizeThinkingLevel, THINKING_L
|
|
|
60
61
|
export { createMemoryToolEffectStore, ToolEffectError } from "./tool-effects.js";
|
|
61
62
|
export { DEFAULT_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, DEFAULT_TOOL_RESULT_FOLD_MIN_AGE_TURNS, DEFAULT_TOOL_RESULT_FOLD_MIN_BYTES, foldedToolResultHeader, foldToolResultHistory, foldToolResults, formatFoldedToolResult, HARD_TOOL_RESULT_FOLD_MAX_SUMMARY_BYTES, resolveToolResultFold, TOOL_RESULT_FOLD_TURN_METADATA_KEY, } from "./tool-result-fold.js";
|
|
62
63
|
export { createToolParameterValidator, createToolRegistry, dispatchToolCall, filterTools } from "./tools.js";
|
|
64
|
+
export { trimTrailingSlashes } from "./trim-trailing-slashes.js";
|
|
63
65
|
export { resolveUseCaseModel, resolveUseCaseModelBinding, useCaseCredentialProviderId, } from "./use-case-model.js";
|
|
64
66
|
export const name = "prism";
|
|
65
|
-
export const version = "0.3.
|
|
67
|
+
export const version = "0.3.1";
|
|
66
68
|
export const description = "Agent harness for AI providers, agents, sessions, and tools.";
|
|
67
69
|
//# sourceMappingURL=index.js.map
|
package/dist/input.js
CHANGED
|
@@ -41,16 +41,22 @@ export function createDefaultPromptBuilder() {
|
|
|
41
41
|
// Tool-capable models receive schemas via request.tools; the text list only serves
|
|
42
42
|
// text-only (or unknown-capability) models — duplicating it doubles tool tokens per turn.
|
|
43
43
|
const tools = request.model?.capabilities?.tools === true ? undefined : request.tools;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
...request.messages
|
|
53
|
-
|
|
44
|
+
const context = contextMessages(request.context);
|
|
45
|
+
const skills = buildSkillMessages(request.skills, {
|
|
46
|
+
disclosure: request.skillsDisclosure,
|
|
47
|
+
loaded: request.loadedSkills,
|
|
48
|
+
demotedBodies: request.demotedSkillBodies?.length ? new Set(request.demotedSkillBodies) : undefined,
|
|
49
|
+
});
|
|
50
|
+
const declarations = toolMessages(tools);
|
|
51
|
+
if ((request.inputLayout ?? "cache_aware") === "legacy") {
|
|
52
|
+
return [...context, ...skills, ...declarations, ...request.messages];
|
|
53
|
+
}
|
|
54
|
+
// Default input assembly keeps stable instruction messages at the front. Keep that
|
|
55
|
+
// boundary ahead of dynamic context/skills; legacy retains the old whole-prompt order.
|
|
56
|
+
const firstNonSystem = request.messages.findIndex((message) => message.role !== "system");
|
|
57
|
+
const stable = firstNonSystem < 0 ? request.messages : request.messages.slice(0, firstNonSystem);
|
|
58
|
+
const dynamic = firstNonSystem < 0 ? [] : request.messages.slice(firstNonSystem);
|
|
59
|
+
return [...stable, ...context, ...skills, ...declarations, ...dynamic];
|
|
54
60
|
},
|
|
55
61
|
};
|
|
56
62
|
}
|
|
@@ -165,6 +171,7 @@ export async function assembleProviderInput(options) {
|
|
|
165
171
|
const promptRequest = options.middleware
|
|
166
172
|
? await options.middleware.run("prompt_build", {
|
|
167
173
|
messages,
|
|
174
|
+
inputLayout: layout,
|
|
168
175
|
context,
|
|
169
176
|
skills,
|
|
170
177
|
skillsDisclosure: options.skillsDisclosure,
|
|
@@ -176,6 +183,7 @@ export async function assembleProviderInput(options) {
|
|
|
176
183
|
})
|
|
177
184
|
: {
|
|
178
185
|
messages,
|
|
186
|
+
inputLayout: layout,
|
|
179
187
|
context,
|
|
180
188
|
skills,
|
|
181
189
|
skillsDisclosure: options.skillsDisclosure,
|
|
@@ -185,7 +193,7 @@ export async function assembleProviderInput(options) {
|
|
|
185
193
|
metadata: options.metadata,
|
|
186
194
|
signal: options.signal,
|
|
187
195
|
};
|
|
188
|
-
const providerMessages = await promptBuilder.build({ ...promptRequest, tools, model: options.model });
|
|
196
|
+
const providerMessages = await promptBuilder.build({ ...promptRequest, inputLayout: layout, tools, model: options.model });
|
|
189
197
|
assertMessagesSupportModelCapabilities(options.model, providerMessages);
|
|
190
198
|
const metadata = budgetReport ? { ...options.metadata, [CONTEXT_BUDGET_REPORT_METADATA_KEY]: budgetReport } : options.metadata;
|
|
191
199
|
return {
|
|
@@ -14,7 +14,9 @@ export function createJsonlSessionStore(pathOrOptions) {
|
|
|
14
14
|
const idempotencySeen = new Set();
|
|
15
15
|
return {
|
|
16
16
|
append(entry, appendOptions) {
|
|
17
|
-
|
|
17
|
+
const operation = appendChain
|
|
18
|
+
.catch(() => undefined)
|
|
19
|
+
.then(async () => {
|
|
18
20
|
if (options.createDirectory !== false)
|
|
19
21
|
await mkdir(dirname(path), { recursive: true });
|
|
20
22
|
const readResult = await readJsonlSessionEntries(path);
|
|
@@ -29,7 +31,8 @@ export function createJsonlSessionStore(pathOrOptions) {
|
|
|
29
31
|
if (dedupKey !== undefined && idempotencySeen.has(dedupKey)) {
|
|
30
32
|
throw new SessionAppendConflictError({ code: SESSION_APPEND_CONFLICT_CODE, idempotencyDuplicate: true });
|
|
31
33
|
}
|
|
32
|
-
if (appendOptions?.expectedParentId !== undefined &&
|
|
34
|
+
if (appendOptions?.expectedParentId !== undefined &&
|
|
35
|
+
!entries.some((existing) => existing.id === appendOptions.expectedParentId)) {
|
|
33
36
|
throw new SessionAppendConflictError({ code: SESSION_APPEND_CONFLICT_CODE, expectedParentId: appendOptions.expectedParentId });
|
|
34
37
|
}
|
|
35
38
|
if (entries.some((existing) => existing.id === entry.id))
|
|
@@ -38,7 +41,8 @@ export function createJsonlSessionStore(pathOrOptions) {
|
|
|
38
41
|
idempotencySeen.add(dedupKey);
|
|
39
42
|
await appendFile(path, `${JSON.stringify(entry)}\n`, "utf8");
|
|
40
43
|
});
|
|
41
|
-
|
|
44
|
+
appendChain = operation.catch(() => undefined);
|
|
45
|
+
return operation;
|
|
42
46
|
},
|
|
43
47
|
async list(sessionId) {
|
|
44
48
|
return (await readEntries(path)).filter((entry) => entry.sessionId === sessionId);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveCredentialValue } from "../credentials.js";
|
|
2
2
|
import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, toolCallFromArgumentsText, } from "../provider-events.js";
|
|
3
3
|
import { assertStructuredOutputRequestSupported } from "../structured-output.js";
|
|
4
|
+
import { trimTrailingSlashes } from "../trim-trailing-slashes.js";
|
|
4
5
|
import { applyOpenAIChatStructuredOutput, assertOpenAIChatMessage, mapOpenAIChatUsage, serializeOpenAIChatMessage, serializeOpenAITool, } from "./openai-primitives.js";
|
|
5
6
|
import { httpStatusError, ProviderTransportError, readBoundedResponseText, readSseEvents } from "./transport.js";
|
|
6
7
|
/**
|
|
@@ -100,7 +101,7 @@ export function createOpenAICompatibleProvider(options) {
|
|
|
100
101
|
try {
|
|
101
102
|
const url = typeof options.chatCompletionsUrl === "function"
|
|
102
103
|
? options.chatCompletionsUrl(request)
|
|
103
|
-
: (options.chatCompletionsUrl ?? `${options.baseUrl
|
|
104
|
+
: (options.chatCompletionsUrl ?? `${trimTrailingSlashes(options.baseUrl)}/chat/completions`);
|
|
104
105
|
const authStyle = options.authStyle ?? "bearer";
|
|
105
106
|
const headers = {
|
|
106
107
|
...Object.fromEntries(Object.entries(request.options?.headers ?? {}).filter((entry) => typeof entry[1] === "string")),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalizeJsonSchema } from "./schema.js";
|
|
1
2
|
export function assertOpenAIChatMessage(message, path) {
|
|
2
3
|
if (!message || typeof message !== "object") {
|
|
3
4
|
throw new Error(`Invalid provider message at ${path}: expected object`);
|
|
@@ -46,7 +47,7 @@ export function serializeOpenAITool(tool) {
|
|
|
46
47
|
function: {
|
|
47
48
|
name: tool.name,
|
|
48
49
|
description: tool.description,
|
|
49
|
-
parameters: tool.parameters ?? { type: "object" },
|
|
50
|
+
parameters: canonicalizeJsonSchema(tool.parameters ?? { type: "object" }),
|
|
50
51
|
},
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON Schema clone for tool/function parameters.
|
|
3
|
+
* Sorts object keys and unordered `required` names. Leaves semantic arrays
|
|
4
|
+
* (`prefixItems`, `examples`, `enum`, tuple `items`) in caller order.
|
|
5
|
+
* Does not resolve `$ref`, mutate input, or enforce schema bounds.
|
|
6
|
+
*/
|
|
7
|
+
export declare function canonicalizeJsonSchema(value: unknown): unknown;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON Schema clone for tool/function parameters.
|
|
3
|
+
* Sorts object keys and unordered `required` names. Leaves semantic arrays
|
|
4
|
+
* (`prefixItems`, `examples`, `enum`, tuple `items`) in caller order.
|
|
5
|
+
* Does not resolve `$ref`, mutate input, or enforce schema bounds.
|
|
6
|
+
*/
|
|
7
|
+
export function canonicalizeJsonSchema(value) {
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return value.map(canonicalizeJsonSchema);
|
|
10
|
+
if (!value || typeof value !== "object")
|
|
11
|
+
return value;
|
|
12
|
+
return Object.fromEntries(Object.entries(value)
|
|
13
|
+
.sort(([left], [right]) => compareKey(left, right))
|
|
14
|
+
.map(([key, item]) => [key, key === "required" ? canonicalizeRequired(item) : canonicalizeJsonSchema(item)]));
|
|
15
|
+
}
|
|
16
|
+
function canonicalizeRequired(item) {
|
|
17
|
+
if (Array.isArray(item) && item.every((entry) => typeof entry === "string")) {
|
|
18
|
+
return [...item].sort(compareKey);
|
|
19
|
+
}
|
|
20
|
+
return canonicalizeJsonSchema(item);
|
|
21
|
+
}
|
|
22
|
+
function compareKey(left, right) {
|
|
23
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -38,6 +38,16 @@ export declare function assertProviderStreamConforms(options: ProviderStreamConf
|
|
|
38
38
|
export declare function assertAbortIsObserved(options: ProviderAbortConformanceOptions): Promise<void>;
|
|
39
39
|
export declare function assertToolCallDeltasReconstruct(events: readonly ProviderEvent[], expected: readonly ToolCallDeltaExpectation[]): readonly ToolCallContent[];
|
|
40
40
|
export declare function assertSerializedRequestCoversContent(request: ProviderRequest, body: unknown, options?: SerializedContentCoverageOptions): void;
|
|
41
|
+
export declare function assertCanonicalToolParameters(serialized: unknown, original: unknown): void;
|
|
41
42
|
export declare function assertProviderOwnedHeadersWin(captured: Headers, options: ProviderHeaderOwnershipConformanceOptions): void;
|
|
42
43
|
export declare function assertNoSecretLeak(events: readonly ProviderEvent[], secrets: readonly string[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* Implicit/none-cache providers must serialize no foreign cache fields: implicit
|
|
46
|
+
* caching works by byte-stable prefix reuse, not request payloads. `allowed` names
|
|
47
|
+
* fields the provider documents for that route (e.g. `cachedContent` via the host
|
|
48
|
+
* `extra.cachedContent` escape hatch on Gemini).
|
|
49
|
+
*/
|
|
50
|
+
export declare function assertNoForeignCacheFields(body: unknown, allowed?: readonly string[]): void;
|
|
51
|
+
/** Provider construction and setup must perform zero network calls; discovery and streams are caller-gated. */
|
|
52
|
+
export declare function assertNoFetches(calls: readonly unknown[]): void;
|
|
43
53
|
export declare function assertUsageAccounting(events: readonly ProviderEvent[], expected: Usage): Usage;
|