@arnilo/prism 0.3.0 → 0.3.2

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +3 -1
  3. package/dist/agent-definitions.js +4 -1
  4. package/dist/agent-loops.js +45 -8
  5. package/dist/agent-session/helpers.js +2 -2
  6. package/dist/cache-helpers.d.ts +11 -0
  7. package/dist/cache-helpers.js +29 -5
  8. package/dist/cli-provider-add.js +2 -1
  9. package/dist/context-budget.js +9 -6
  10. package/dist/contracts-core/agent.d.ts +25 -2
  11. package/dist/contracts-core/provider.d.ts +2 -0
  12. package/dist/event-multiplexer.js +0 -4
  13. package/dist/index.d.ts +6 -4
  14. package/dist/index.js +5 -3
  15. package/dist/input.js +19 -11
  16. package/dist/node/session-store-jsonl.js +7 -3
  17. package/dist/providers/openai-compatible.js +2 -1
  18. package/dist/providers/openai-primitives.js +2 -1
  19. package/dist/providers/schema.d.ts +7 -0
  20. package/dist/providers/schema.js +25 -0
  21. package/dist/rpc.d.ts +4 -1
  22. package/dist/rpc.js +5 -1
  23. package/dist/testing/provider-conformance.d.ts +10 -0
  24. package/dist/testing/provider-conformance.js +37 -0
  25. package/dist/trim-trailing-slashes.d.ts +8 -0
  26. package/dist/trim-trailing-slashes.js +14 -0
  27. package/docs/0.1.0-readiness.md +1 -1
  28. package/docs/acp.md +1 -0
  29. package/docs/ag-ui.md +1 -0
  30. package/docs/agent-definitions.md +1 -1
  31. package/docs/agent-loops.md +3 -0
  32. package/docs/agent-session-runtime.md +1 -0
  33. package/docs/browser-automation.md +1 -0
  34. package/docs/coding-agent-tools.md +7 -1
  35. package/docs/compaction-and-retry.md +3 -0
  36. package/docs/compaction-observational-memory.md +47 -0
  37. package/docs/database-persistence.md +1 -1
  38. package/docs/extension-authoring.md +42 -0
  39. package/docs/graft.md +125 -0
  40. package/docs/host-security.md +3 -1
  41. package/docs/index.md +15 -13
  42. package/docs/input-and-prompt-assembly.md +11 -6
  43. package/docs/instruction-injection.md +1 -1
  44. package/docs/mcp-tools.md +1 -0
  45. package/docs/migration.md +10 -0
  46. package/docs/node-jsonl-session-store.md +1 -1
  47. package/docs/obscura.md +175 -0
  48. package/docs/observability.md +21 -1
  49. package/docs/performance.md +58 -4
  50. package/docs/ponytail.md +1 -1
  51. package/docs/provider-caching.md +13 -11
  52. package/docs/provider-conformance.md +6 -0
  53. package/docs/provider-packages.md +1 -1
  54. package/docs/provider-primitives.md +15 -2
  55. package/docs/providers/ai-sdk.md +1 -1
  56. package/docs/providers/anthropic.md +1 -1
  57. package/docs/providers/azure.md +1 -0
  58. package/docs/providers/bedrock.md +1 -0
  59. package/docs/providers/kimi.md +2 -1
  60. package/docs/providers/openai.md +19 -7
  61. package/docs/providers/opencode-go.md +3 -1
  62. package/docs/providers/openrouter.md +4 -3
  63. package/docs/providers/vertex.md +1 -0
  64. package/docs/public-contracts.md +3 -2
  65. package/docs/rag.md +55 -8
  66. package/docs/release-and-install.md +63 -7
  67. package/docs/server.md +1 -0
  68. package/docs/supervisors.md +10 -2
  69. package/docs/system-prompts.md +1 -1
  70. package/docs/tools.md +1 -1
  71. package/docs/web-tools.md +2 -0
  72. package/docs/wiki.md +154 -0
  73. package/docs/workflows.md +38 -4
  74. package/docs/working-and-semantic-memory.md +20 -0
  75. package/package.json +12 -5
  76. package/docs/api-page-template.md +0 -32
  77. package/docs/release-0.2.7-evidence.md +0 -514
package/CHANGELOG.md CHANGED
@@ -1,3 +1,71 @@
1
+ ## [0.3.2] - 2026-08-29 (plan 050)
2
+
3
+ ### Added
4
+ - **OKF adoption (`@arnilo/prism-wiki`)**: wiki-init/refresh/lint emit and validate
5
+ OKF v0.2 bundles (Karpathy prompt retained). See `docs/wiki.md`.
6
+ - **DOCS-1 (Clay integration findings)**: three integrator contracts, in place on
7
+ the pages that own them — resume-aware workflow nodes (`ctx.resume` or silent
8
+ re-suspend) in `docs/workflows.md`; supervisor child factories return `Agent`
9
+ not `AgentSession` (`SupervisorError: child "<id>" factory must return an
10
+ Agent, got <type>`) plus durable-store nested approvals in `docs/supervisors.md`;
11
+ task-boundary `session.compact()` fails closed during an active run in
12
+ `docs/compaction-and-retry.md`. Each block links `examples/autonomous-coding-loop.ts`.
13
+ - **FEATURE-2 (Clay integration findings)**: documented bounded iterate-until-done
14
+ host-loop pattern in `docs/workflows.md` — one `runWorkflow` per iteration,
15
+ iteration state in workflow inputs, explicit termination predicate and budgets,
16
+ typed `BudgetExhaustedError` (fail-closed, never a hang), `replayWorkflow` per
17
+ iteration run id. Seeded by `examples/autonomous-coding-loop.ts`. Plan 045 `loop`
18
+ node remains the future in-graph primitive; this intake ships the docs+example
19
+ minimum only.
20
+ - **FEATURE-6 (Clay integration findings)**: composite `examples/autonomous-coding-loop.ts`
21
+ conformance reference — goal → roadmap → per-task supervisor children (per-child
22
+ models) → `runCodingGoalVerify`-style validation → observational-memory attach +
23
+ task-boundary compact + recall → human gate with simulated restart → host-side
24
+ bounded iterate-until-done with deterministic budget exhaustion. Mock providers
25
+ only; no credentials or network.
26
+ - **FEATURE-3 (Clay integration findings)**: host-opt-in command driver hooks.
27
+ `CommandExecutionContext` gains an optional `drivers?: CommandDrivers`
28
+ (`startRun` / `startWorkflow` / `steer` — typed minimal handles returning
29
+ `AgentRunResult`-shaped results / workflow run id + status) so a contributed
30
+ command can act through host-injected capabilities instead of being
31
+ re-implemented host-side. The RPC session factory accepts `drivers` and
32
+ forwards them at the single command-execution site; absent drivers leave
33
+ the context shape unchanged (no key, no allocation). Drivers are
34
+ host-injected capabilities, never package-supplied.
35
+ ### Fixed
36
+ - **FEATURE-1 (Clay integration findings)**: `resolveAgentDefinition` no longer
37
+ throws `Agent "<name>" has no model` when the declarative definition omits
38
+ `model` but `context.overrides.model` supplies one — the fallback is a
39
+ single `??` at `buildBaseConfig`, the `create()` path is unchanged, and a
40
+ definition with neither source still fails closed.
41
+
42
+ ## [0.3.1] - 2026-08-29
43
+
44
+ > 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).
45
+
46
+ ### Added
47
+ - `@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.
48
+ - 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.
49
+ - 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`.
50
+
51
+ ## [0.3.1] - 2026-08-26
52
+
53
+ ### Added
54
+ - **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):
55
+ - **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.
56
+ - **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.
57
+ - **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`.
58
+ - **P4 content-hash skip** — `replaceSource({ contentHash })` skips unchanged sources (`{ indexed: 0, skipped: true }`) and reuses embeddings when chunk text matches (`reuseEmbeddings`, `isValidContentHash`).
59
+ - **P5 heading metadata** — `chunkMarkdown` stamps `metadata.heading` (parent-first ATX stack) unless the caller supplied one; parser metadata (`page`, `section`, …) copies onto chunks.
60
+ - **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.
61
+ - **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.
62
+ - **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.
63
+ - **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.
64
+ - 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`).
65
+
66
+ ### Changed
67
+ - 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.
68
+
1
69
  ## [0.3.0] - 2026-08-20
2
70
 
3
71
  ### 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, and antigravity-agent |
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
 
@@ -20,7 +20,10 @@ export function resolveAgentDefinition(def, context) {
20
20
  return createAgent(applyConfigOverrides(baseConfig, context.overrides));
21
21
  }
22
22
  function buildBaseConfig(def, context) {
23
- const model = resolveModel(def.name, def.model, context);
23
+ // Model precedence: explicit def.model wins; a definition without a model
24
+ // falls back to context.overrides.model (host-injected selection). Neither
25
+ // present still fails closed in resolveModel.
26
+ const model = resolveModel(def.name, def.model ?? context.overrides?.model, context);
24
27
  const tools = resolveTools(def.tools, context);
25
28
  const skills = resolveSkills(def.skills, tools, context);
26
29
  return {
@@ -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: per-run state hoisted to factory scope so snapshot/restore can capture it;
84
- // resolveLoop invokes this factory once per run, so there is no cross-run leakage.
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
- throwIfAborted(ctx.signal);
266
- const index = nextIndex;
267
- nextIndex += 1;
268
- if (index >= calls.length)
292
+ if (stopped)
269
293
  return;
270
- results[index] = await ctx.dispatchToolCall(calls[index]);
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.all(workers);
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
- return jsonTextEncoder.encode(JSON.stringify(value)).byteLength;
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");
@@ -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;
@@ -1,8 +1,15 @@
1
1
  export function sanitizeCacheKey(value, maxLength) {
2
- const key = value
3
- ?.replace(/[^A-Za-z0-9_.:-]+/g, "-")
4
- .replace(/^-+|-+$/g, "")
5
- .slice(0, Math.max(0, maxLength));
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");
@@ -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.replace(/\/+$/, ""),
212
+ __BASE_URL__: trimTrailingSlashes(input.baseUrl),
212
213
  __ENV_KEY__: input.envKey,
213
214
  __MODEL_ID__: input.model,
214
215
  };
@@ -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 new TextEncoder().encode(text).byteLength;
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, drop oldest first (shift).
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 > 0) {
109
- const message = groups.history.shift();
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) {
@@ -1,7 +1,7 @@
1
1
  /** Contracts-core agent family (0.2.5 plan 025 Task 1 split).
2
2
  * Moved verbatim from contracts-core.ts; public surface unchanged behind the barrel. */
3
- import type { InputAssemblyLayout, RunLedger, ToolDefinition, ToolEffectStore, ToolRegistry } from "../contracts-protocol.js";
4
- import type { AgentRunStateOptions, AgentSession } from "../contracts-run-state.js";
3
+ import type { InputAssemblyLayout, RunLedger, RunOptions, ToolDefinition, ToolEffectStore, ToolRegistry } from "../contracts-protocol.js";
4
+ import type { AgentRunResult, AgentRunStateOptions, AgentSession } from "../contracts-run-state.js";
5
5
  import type { ContributionRegistries } from "../contributions.js";
6
6
  import type { MiddlewareRegistry } from "../middleware.js";
7
7
  import type { SecretRedactor } from "../redaction.js";
@@ -147,6 +147,27 @@ export interface CommandExecutionContext {
147
147
  readonly runId?: string;
148
148
  readonly signal?: AbortSignal;
149
149
  readonly metadata?: Readonly<Record<string, unknown>>;
150
+ /** Host-injected driver capabilities (host-opt-in; never package-supplied).
151
+ * Absent in hosts that don't supply them — commands stay inert data there. */
152
+ readonly drivers?: CommandDrivers;
153
+ }
154
+ /** Minimal run reference returned by {@link CommandDrivers.startWorkflow}. */
155
+ export interface CommandWorkflowRun {
156
+ readonly runId: string;
157
+ readonly status: string;
158
+ }
159
+ /** Host-injected capabilities a contributed command may act through when the
160
+ * host opts in. Drivers are supplied by the host at context construction
161
+ * (e.g. the RPC session factory), never by packages; core only types and
162
+ * forwards them. `metadata.trust` labeling is unaffected. */
163
+ export interface CommandDrivers {
164
+ /** Start a session run. */
165
+ startRun(input: string, options?: RunOptions): Promise<AgentRunResult> | AgentRunResult;
166
+ /** Start a run on a host-understood orchestration definition. Returns at
167
+ * minimum the run id and status; richer host shapes pass through as-is. */
168
+ startWorkflow(definition: object, input: unknown, options?: Readonly<Record<string, unknown>>): Promise<CommandWorkflowRun>;
169
+ /** Steer an active run with additional input. */
170
+ steer(runId: string, input: string): Promise<void> | void;
150
171
  }
151
172
  export interface CommandResult {
152
173
  readonly name: string;
@@ -225,6 +246,8 @@ export interface PromptBuilder {
225
246
  }
226
247
  export interface PromptBuildRequest {
227
248
  readonly messages: readonly Message[];
249
+ /** Input layout selected by the host; the default builder uses cache-aware ordering unless legacy is explicit. */
250
+ readonly inputLayout?: InputAssemblyLayout;
228
251
  readonly context?: readonly ContextBlock[];
229
252
  readonly skills?: readonly Skill[];
230
253
  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.0";
118
+ export declare const version = "0.3.2";
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.0";
67
+ export const version = "0.3.2";
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
- return [
45
- ...contextMessages(request.context),
46
- ...buildSkillMessages(request.skills, {
47
- disclosure: request.skillsDisclosure,
48
- loaded: request.loadedSkills,
49
- demotedBodies: request.demotedSkillBodies?.length ? new Set(request.demotedSkillBodies) : undefined,
50
- }),
51
- ...toolMessages(tools),
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
- appendChain = appendChain.then(async () => {
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 && !entries.some((existing) => existing.id === appendOptions.expectedParentId)) {
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
- return appendChain;
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.replace(/\/+$/, "")}/chat/completions`);
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")),