@arnilo/prism 0.0.8 → 0.0.11
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 +44 -3
- package/dist/agent-loops.d.ts +1 -0
- package/dist/agent-loops.js +44 -5
- package/dist/agents.js +141 -7
- package/dist/context-budget.d.ts +63 -0
- package/dist/context-budget.js +235 -0
- package/dist/contracts.d.ts +107 -0
- package/dist/contracts.js +77 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +5 -4
- package/dist/input.d.ts +3 -0
- package/dist/input.js +71 -28
- package/dist/node/session-store-jsonl.js +4 -1
- package/dist/provider-events.d.ts +2 -0
- package/dist/provider-events.js +21 -13
- package/dist/providers/openai-compatible.js +8 -5
- package/dist/providers/transport.d.ts +10 -1
- package/dist/providers/transport.js +24 -8
- package/dist/rpc.js +13 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +174 -4
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +18 -0
- package/dist/testing/persistence-schema.d.ts +1 -1
- package/dist/testing/persistence-schema.js +8 -2
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +36 -1
- package/dist/tools.js +2 -0
- package/docs/agent-events.md +3 -2
- package/docs/agent-loops.md +10 -3
- package/docs/agent-session-runtime.md +5 -1
- package/docs/browser-automation.md +124 -0
- package/docs/cli-rpc.md +2 -1
- package/docs/coding-agent-tools.md +178 -14
- package/docs/coding-security.md +84 -11
- package/docs/evaluations.md +13 -2
- package/docs/guardrails.md +2 -1
- package/docs/host-security.md +4 -2
- package/docs/index.md +19 -15
- package/docs/input-and-prompt-assembly.md +4 -1
- package/docs/migration.md +84 -0
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/performance.md +46 -0
- package/docs/postgres-persistence.md +3 -3
- package/docs/provider-conformance.md +1 -1
- package/docs/provider-packages.md +1 -1
- package/docs/provider-primitives.md +7 -1
- package/docs/providers/anthropic.md +92 -0
- package/docs/providers/google.md +87 -0
- package/docs/public-contracts.md +4 -0
- package/docs/release-and-install.md +197 -64
- package/docs/review-coverage-2026-07-20-phase-4.md +175 -0
- package/docs/review-coverage-2026-07-21-phase-5.md +172 -0
- package/docs/review-coverage-2026-07-22-phase-6.md +209 -0
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +40 -1
- package/docs/sqlite-persistence.md +3 -3
- package/docs/structured-output.md +9 -3
- package/docs/tools.md +3 -0
- package/docs/web-tools.md +1 -1
- package/docs/workflows.md +3 -0
- package/package.json +6 -5
package/dist/input.js
CHANGED
|
@@ -3,22 +3,12 @@ import { assertMessagesSupportModelCapabilities } from "./content.js";
|
|
|
3
3
|
import { loadTextResource } from "./resources.js";
|
|
4
4
|
import { composeSystemPrompt } from "./system-prompts.js";
|
|
5
5
|
import { runInstructionInjectors } from "./instruction-injection.js";
|
|
6
|
+
import { applyContextBudget, CONTEXT_BUDGET_REPORT_METADATA_KEY, } from "./context-budget.js";
|
|
6
7
|
export function createDefaultInputBuilder() {
|
|
7
8
|
return {
|
|
8
9
|
name: "default-input",
|
|
9
10
|
async build(input, context = {}) {
|
|
10
|
-
const groups =
|
|
11
|
-
instructions: [
|
|
12
|
-
...instructionMessages(context.systemInstructions, "System instruction"),
|
|
13
|
-
...instructionMessages(context.developerInstructions, "Developer instruction"),
|
|
14
|
-
...customInstructionMessages(context.instructions),
|
|
15
|
-
],
|
|
16
|
-
summaries: summaryMessages(context.summaries),
|
|
17
|
-
history: [...(context.history ?? [])],
|
|
18
|
-
input: inputMessages(input),
|
|
19
|
-
attachments: await attachmentMessages(context),
|
|
20
|
-
toolResults: toolResultMessages(context.toolResults),
|
|
21
|
-
};
|
|
11
|
+
const groups = await buildDefaultInputMessageGroups(input, context);
|
|
22
12
|
const messages = flattenInputGroups(groups, context.inputLayout ?? "legacy");
|
|
23
13
|
return context.middleware ? context.middleware.run("input_assembly", messages) : messages;
|
|
24
14
|
},
|
|
@@ -65,7 +55,6 @@ export function renderPromptTemplate(template, variables, options = {}) {
|
|
|
65
55
|
});
|
|
66
56
|
}
|
|
67
57
|
export async function assembleProviderInput(options) {
|
|
68
|
-
const inputBuilder = options.inputBuilder ?? createDefaultInputBuilder();
|
|
69
58
|
const baseContext = {
|
|
70
59
|
sessionId: options.sessionId,
|
|
71
60
|
runId: options.runId,
|
|
@@ -92,44 +81,98 @@ export async function assembleProviderInput(options) {
|
|
|
92
81
|
? composeSystemPrompt(injectorContribs.instructions, { base: options.systemInstructions })
|
|
93
82
|
: options.systemInstructions;
|
|
94
83
|
const buildContext = { ...options, ...baseContext, systemInstructions };
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
84
|
+
const layout = buildContext.inputLayout ?? "legacy";
|
|
85
|
+
let messages;
|
|
86
|
+
let context;
|
|
87
|
+
let skills = options.skills;
|
|
88
|
+
let tools = options.tools;
|
|
89
|
+
let budgetReport;
|
|
90
|
+
if (options.contextBudget) {
|
|
91
|
+
// ponytail: budget path always uses default groups so eviction kinds stay deterministic;
|
|
92
|
+
// custom inputBuilder still honored when contextBudget is absent.
|
|
93
|
+
const groups = await buildDefaultInputMessageGroups(options.input, buildContext);
|
|
94
|
+
context = await resolveContextProviders({
|
|
95
|
+
providers: options.contextProviders,
|
|
96
|
+
messages: flattenInputGroups(groups, layout),
|
|
97
|
+
injectedBlocks: injectorContribs.contextBlocks.length ? injectorContribs.contextBlocks : undefined,
|
|
98
|
+
middleware: options.middleware,
|
|
99
|
+
...baseContext,
|
|
100
|
+
});
|
|
101
|
+
const applied = applyContextBudget({
|
|
102
|
+
groups,
|
|
103
|
+
context,
|
|
104
|
+
skills: skills ?? [],
|
|
105
|
+
tools,
|
|
106
|
+
budget: options.contextBudget,
|
|
107
|
+
layout,
|
|
108
|
+
});
|
|
109
|
+
messages = flattenInputGroups(applied.groups, layout);
|
|
110
|
+
if (buildContext.middleware)
|
|
111
|
+
messages = await buildContext.middleware.run("input_assembly", messages);
|
|
112
|
+
context = applied.context;
|
|
113
|
+
skills = applied.skills;
|
|
114
|
+
tools = applied.tools;
|
|
115
|
+
if (options.contextBudget.reportOmissions)
|
|
116
|
+
budgetReport = applied.report;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
const inputBuilder = options.inputBuilder ?? createDefaultInputBuilder();
|
|
120
|
+
messages = await inputBuilder.build(options.input, buildContext);
|
|
121
|
+
context = await resolveContextProviders({
|
|
122
|
+
providers: options.contextProviders,
|
|
123
|
+
messages,
|
|
124
|
+
injectedBlocks: injectorContribs.contextBlocks.length ? injectorContribs.contextBlocks : undefined,
|
|
125
|
+
middleware: options.middleware,
|
|
126
|
+
...baseContext,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
103
129
|
const promptBuilder = options.promptBuilder ?? createDefaultPromptBuilder();
|
|
104
130
|
const promptRequest = options.middleware
|
|
105
131
|
? await options.middleware.run("prompt_build", {
|
|
106
132
|
messages,
|
|
107
133
|
context,
|
|
108
|
-
skills
|
|
109
|
-
tools
|
|
134
|
+
skills,
|
|
135
|
+
tools,
|
|
110
136
|
metadata: options.metadata,
|
|
111
137
|
signal: options.signal,
|
|
112
138
|
})
|
|
113
139
|
: {
|
|
114
140
|
messages,
|
|
115
141
|
context,
|
|
116
|
-
skills
|
|
117
|
-
tools
|
|
142
|
+
skills,
|
|
143
|
+
tools,
|
|
118
144
|
metadata: options.metadata,
|
|
119
145
|
signal: options.signal,
|
|
120
146
|
};
|
|
121
|
-
const providerMessages = await promptBuilder.build({ ...promptRequest, tools
|
|
147
|
+
const providerMessages = await promptBuilder.build({ ...promptRequest, tools });
|
|
122
148
|
assertMessagesSupportModelCapabilities(options.model, providerMessages);
|
|
149
|
+
const metadata = budgetReport
|
|
150
|
+
? { ...options.metadata, [CONTEXT_BUDGET_REPORT_METADATA_KEY]: budgetReport }
|
|
151
|
+
: options.metadata;
|
|
123
152
|
return {
|
|
124
153
|
model: options.model,
|
|
125
154
|
messages: providerMessages,
|
|
126
|
-
tools
|
|
155
|
+
tools,
|
|
127
156
|
context,
|
|
128
157
|
options: options.providerOptions,
|
|
129
|
-
metadata
|
|
158
|
+
metadata,
|
|
130
159
|
signal: options.signal,
|
|
131
160
|
};
|
|
132
161
|
}
|
|
162
|
+
async function buildDefaultInputMessageGroups(input, context) {
|
|
163
|
+
return {
|
|
164
|
+
instructions: [
|
|
165
|
+
...instructionMessages(context.systemInstructions, "System instruction"),
|
|
166
|
+
...instructionMessages(context.developerInstructions, "Developer instruction"),
|
|
167
|
+
...customInstructionMessages(context.instructions),
|
|
168
|
+
],
|
|
169
|
+
summaries: summaryMessages(context.summaries),
|
|
170
|
+
history: [...(context.history ?? [])],
|
|
171
|
+
input: inputMessages(input),
|
|
172
|
+
attachments: await attachmentMessages(context),
|
|
173
|
+
toolResults: toolResultMessages(context.toolResults),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
133
176
|
function flattenInputGroups(groups, layout) {
|
|
134
177
|
switch (layout) {
|
|
135
178
|
case "cache_aware":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, appendFile } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import { SESSION_APPEND_CONFLICT_CODE, SessionAppendConflictError, isSessionEntryKind, SESSION_ENTRY_SCHEMA_VERSION } from "../contracts.js";
|
|
3
|
+
import { SESSION_APPEND_CONFLICT_CODE, SessionAppendConflictError, SessionSearchUnsupportedError, isSessionEntryKind, SESSION_ENTRY_SCHEMA_VERSION, } from "../contracts.js";
|
|
4
4
|
import { isNodeErrorCode } from "./config.js";
|
|
5
5
|
export function createJsonlSessionStore(pathOrOptions) {
|
|
6
6
|
const options = typeof pathOrOptions === "string" ? { path: pathOrOptions, createDirectory: true } : pathOrOptions;
|
|
@@ -46,6 +46,9 @@ export function createJsonlSessionStore(pathOrOptions) {
|
|
|
46
46
|
async get(id) {
|
|
47
47
|
return findEntry(path, id);
|
|
48
48
|
},
|
|
49
|
+
async searchSessions() {
|
|
50
|
+
throw new SessionSearchUnsupportedError("JSONL session store does not support searchSessions");
|
|
51
|
+
},
|
|
49
52
|
};
|
|
50
53
|
}
|
|
51
54
|
/** Read a JSONL session file and return both valid entries and per-line parse errors. */
|
|
@@ -15,3 +15,5 @@ export declare function providerUsage(usage: Usage): ProviderEvent;
|
|
|
15
15
|
export declare function providerDone(usage?: Usage): ProviderEvent;
|
|
16
16
|
export declare function providerError(error: unknown, secrets?: readonly (string | undefined)[]): ProviderEvent;
|
|
17
17
|
export declare function toolCallContent(id: string, name: string, args?: JsonObject): ToolCallContent;
|
|
18
|
+
/** Build a tool call from streamed arguments text; malformed JSON becomes a blocked call (no throw). */
|
|
19
|
+
export declare function toolCallFromArgumentsText(id: string, name: string, argumentsText: string): ToolCallContent;
|
package/dist/provider-events.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ProviderTransportError, tryParseJsonObjectArguments } from "./providers/transport.js";
|
|
1
2
|
import { errorToErrorInfo } from "./redaction.js";
|
|
2
3
|
export function providerTextDelta(text) {
|
|
3
4
|
return { type: "content_delta", content: { type: "text", text } };
|
|
@@ -32,9 +33,10 @@ export function reconstructToolCallDeltas(events) {
|
|
|
32
33
|
partials.set(event.index, partial);
|
|
33
34
|
}
|
|
34
35
|
return [...partials.entries()].sort(([a], [b]) => a - b).map(([index, partial]) => {
|
|
35
|
-
if (!partial.id || !partial.name)
|
|
36
|
-
throw new
|
|
37
|
-
|
|
36
|
+
if (!partial.id || !partial.name) {
|
|
37
|
+
throw new ProviderTransportError("incomplete_delta", `Incomplete tool call delta at index ${index}`);
|
|
38
|
+
}
|
|
39
|
+
return toolCallFromArgumentsText(partial.id, partial.name, partial.argumentsText);
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
export function providerUsage(usage) {
|
|
@@ -50,15 +52,21 @@ export function providerError(error, secrets = []) {
|
|
|
50
52
|
export function toolCallContent(id, name, args = {}) {
|
|
51
53
|
return { type: "tool_call", id, name, arguments: args };
|
|
52
54
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
55
|
+
/** Build a tool call from streamed arguments text; malformed JSON becomes a blocked call (no throw). */
|
|
56
|
+
export function toolCallFromArgumentsText(id, name, argumentsText) {
|
|
57
|
+
const parsed = tryParseJsonObjectArguments(argumentsText, { toolName: name });
|
|
58
|
+
if (parsed.ok)
|
|
59
|
+
return toolCallContent(id, name, parsed.value);
|
|
60
|
+
return {
|
|
61
|
+
type: "tool_call",
|
|
62
|
+
id,
|
|
63
|
+
name,
|
|
64
|
+
arguments: {},
|
|
65
|
+
argumentsError: {
|
|
66
|
+
name: parsed.error.name,
|
|
67
|
+
message: parsed.error.message,
|
|
68
|
+
code: parsed.error.code,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
63
71
|
}
|
|
64
72
|
//# sourceMappingURL=provider-events.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveCredentialValue } from "../credentials.js";
|
|
2
|
-
import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage,
|
|
2
|
+
import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, toolCallFromArgumentsText, } from "../provider-events.js";
|
|
3
3
|
import { assertOpenAIChatMessage, applyOpenAIChatStructuredOutput, mapOpenAIChatUsage, serializeOpenAIChatMessage, serializeOpenAITool, } from "./openai-primitives.js";
|
|
4
|
-
import {
|
|
4
|
+
import { ProviderTransportError, readBoundedResponseText, readSseData, } from "./transport.js";
|
|
5
5
|
import { assertStructuredOutputRequestSupported } from "../structured-output.js";
|
|
6
6
|
export function createOpenAICompatibleProvider(options) {
|
|
7
7
|
const providerId = options.id ?? "openai-compatible";
|
|
@@ -64,10 +64,13 @@ export function createOpenAICompatibleProvider(options) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
const incomplete = [...tools.entries()].find(([, call]) => !call.id || !call.name);
|
|
68
|
+
if (incomplete) {
|
|
69
|
+
yield providerError(new ProviderTransportError("incomplete_delta", `Incomplete tool call delta at index ${incomplete[0]}`), secrets);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
67
72
|
for (const call of tools.values()) {
|
|
68
|
-
|
|
69
|
-
yield providerToolCall(toolCallContent(call.id, call.name, parseJsonObjectArguments(call.argumentsText, { toolName: call.name })));
|
|
70
|
-
}
|
|
73
|
+
yield providerToolCall(toolCallFromArgumentsText(call.id, call.name, call.argumentsText));
|
|
71
74
|
}
|
|
72
75
|
yield providerDone();
|
|
73
76
|
}
|
|
@@ -14,7 +14,7 @@ export interface SseEvent {
|
|
|
14
14
|
readonly data: string;
|
|
15
15
|
readonly comments?: readonly string[];
|
|
16
16
|
}
|
|
17
|
-
export type ProviderTransportErrorCode = "sse_buffer_overflow" | "sse_event_overflow" | "response_body_overflow" | "aborted" | "invalid_json_arguments";
|
|
17
|
+
export type ProviderTransportErrorCode = "sse_buffer_overflow" | "sse_event_overflow" | "response_body_overflow" | "aborted" | "invalid_json_arguments" | "incomplete_delta";
|
|
18
18
|
export declare class ProviderTransportError extends Error {
|
|
19
19
|
readonly code: ProviderTransportErrorCode;
|
|
20
20
|
readonly limitBytes?: number;
|
|
@@ -36,5 +36,14 @@ export declare function readSseEvents(body: ReadableStream<Uint8Array>, options?
|
|
|
36
36
|
export declare function readSseData(body: ReadableStream<Uint8Array>, options?: ReadSseEventsOptions): AsyncGenerator<string>;
|
|
37
37
|
/** Read a response body with a hard byte ceiling; redacts optional secrets. */
|
|
38
38
|
export declare function readBoundedResponseText(response: Response, options?: ReadBoundedResponseTextOptions): Promise<string>;
|
|
39
|
+
export type ParseJsonObjectArgumentsResult = {
|
|
40
|
+
readonly ok: true;
|
|
41
|
+
readonly value: JsonObject;
|
|
42
|
+
} | {
|
|
43
|
+
readonly ok: false;
|
|
44
|
+
readonly error: ProviderTransportError;
|
|
45
|
+
};
|
|
46
|
+
/** Parse streamed tool arguments without throwing; prefer this for recoverable tool-call recovery. */
|
|
47
|
+
export declare function tryParseJsonObjectArguments(text: string, options?: ParseJsonObjectArgumentsOptions): ParseJsonObjectArgumentsResult;
|
|
39
48
|
/** Parse streamed tool arguments as a JSON object; throws {@link ProviderTransportError} on invalid input. */
|
|
40
49
|
export declare function parseJsonObjectArguments(text: string, options?: ParseJsonObjectArgumentsOptions): JsonObject;
|
|
@@ -197,25 +197,41 @@ export async function readBoundedResponseText(response, options) {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
-
/** Parse streamed tool arguments
|
|
201
|
-
export function
|
|
200
|
+
/** Parse streamed tool arguments without throwing; prefer this for recoverable tool-call recovery. */
|
|
201
|
+
export function tryParseJsonObjectArguments(text, options) {
|
|
202
202
|
const maxBytes = options?.maxBytes ?? DEFAULT_MAX_ARGUMENT_BYTES;
|
|
203
203
|
const suffix = options?.toolName ? ` for tool ${options.toolName}` : "";
|
|
204
204
|
if (!text)
|
|
205
|
-
return {};
|
|
205
|
+
return { ok: true, value: {} };
|
|
206
206
|
if (byteLength(text) > maxBytes) {
|
|
207
|
-
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
error: new ProviderTransportError("invalid_json_arguments", `Tool arguments${suffix} exceeded ${maxBytes} bytes`, maxBytes),
|
|
210
|
+
};
|
|
208
211
|
}
|
|
209
212
|
let parsed;
|
|
210
213
|
try {
|
|
211
214
|
parsed = JSON.parse(text);
|
|
212
215
|
}
|
|
213
|
-
catch
|
|
214
|
-
|
|
216
|
+
catch {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
error: new ProviderTransportError("invalid_json_arguments", `Invalid tool arguments JSON${suffix}`),
|
|
220
|
+
};
|
|
215
221
|
}
|
|
216
222
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
217
|
-
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
error: new ProviderTransportError("invalid_json_arguments", `Tool arguments${suffix} must be a JSON object`),
|
|
226
|
+
};
|
|
218
227
|
}
|
|
219
|
-
return parsed;
|
|
228
|
+
return { ok: true, value: parsed };
|
|
229
|
+
}
|
|
230
|
+
/** Parse streamed tool arguments as a JSON object; throws {@link ProviderTransportError} on invalid input. */
|
|
231
|
+
export function parseJsonObjectArguments(text, options) {
|
|
232
|
+
const result = tryParseJsonObjectArguments(text, options);
|
|
233
|
+
if (!result.ok)
|
|
234
|
+
throw result.error;
|
|
235
|
+
return result.value;
|
|
220
236
|
}
|
|
221
237
|
//# sourceMappingURL=transport.js.map
|
package/dist/rpc.js
CHANGED
|
@@ -82,8 +82,19 @@ async function handleRequest(request, state, stdout, activeRuns) {
|
|
|
82
82
|
activeRuns.set(session.id, { requestId: request.id, promise });
|
|
83
83
|
break;
|
|
84
84
|
}
|
|
85
|
-
case "steer":
|
|
86
|
-
|
|
85
|
+
case "steer": {
|
|
86
|
+
const input = stringParam(request.params, "input") ?? stringParam(request.params, "prompt");
|
|
87
|
+
if (!input)
|
|
88
|
+
throw new Error("steer requires params.input");
|
|
89
|
+
const softInterrupt = request.params?.softInterrupt === true;
|
|
90
|
+
state.current.steer(input, softInterrupt ? { softInterrupt: true } : undefined);
|
|
91
|
+
write(stdout, {
|
|
92
|
+
id: request.id,
|
|
93
|
+
ok: true,
|
|
94
|
+
result: { sessionId: state.current.id },
|
|
95
|
+
});
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
87
98
|
case "abort":
|
|
88
99
|
state.current.abort(stringParam(request.params, "reason"));
|
|
89
100
|
write(stdout, { id: request.id, ok: true, result: { sessionId: state.current.id } });
|
package/dist/session-stores.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Message, type SessionBranchRead, type BranchReader, type SessionEntry, type SessionStore } from "./contracts.js";
|
|
2
2
|
export interface CreateSessionEntryOptions extends Omit<SessionEntry, "id" | "timestamp"> {
|
|
3
3
|
readonly id?: string;
|
|
4
4
|
readonly timestamp?: string;
|
|
@@ -24,4 +24,9 @@ export declare function getSessionBranchEntries(entries: readonly SessionEntry[]
|
|
|
24
24
|
export declare function listSessionBranches(entries: readonly SessionEntry[]): readonly SessionBranch[];
|
|
25
25
|
export declare function rebuildSessionContext(reader: BranchReader, query: SessionBranchRead): Promise<SessionContextSnapshot>;
|
|
26
26
|
export declare function rebuildSessionContext(entries: readonly SessionEntry[], options?: SessionBranchOptions): SessionContextSnapshot;
|
|
27
|
-
export
|
|
27
|
+
export type MemorySessionSearchMode = "linear" | "unsupported";
|
|
28
|
+
export interface CreateMemorySessionStoreOptions {
|
|
29
|
+
/** Default `"linear"`: capped in-process scan. `"unsupported"`: typed throw. */
|
|
30
|
+
readonly sessionSearchMode?: MemorySessionSearchMode;
|
|
31
|
+
}
|
|
32
|
+
export declare function createMemorySessionStore(initialEntries?: readonly SessionEntry[], options?: CreateMemorySessionStoreOptions): SessionStore;
|
package/dist/session-stores.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SESSION_APPEND_CONFLICT_CODE, SessionAppendConflictError } from "./contracts.js";
|
|
1
|
+
import { DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES, DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES, SESSION_APPEND_CONFLICT_CODE, SESSION_SEARCH_WORKSPACE_METADATA_KEY, SessionAppendConflictError, SessionSearchUnsupportedError, resolveSessionSearchQuery, } from "./contracts.js";
|
|
2
2
|
import { createId } from "./ids.js";
|
|
3
3
|
export function createSessionEntry(options) {
|
|
4
4
|
const { createId, now, ...entry } = options;
|
|
@@ -93,16 +93,17 @@ function rebuildSessionContextCore(entries, options = {}) {
|
|
|
93
93
|
}
|
|
94
94
|
return { leafId: branch.at(-1)?.id, entries: branch, messages, summaries };
|
|
95
95
|
}
|
|
96
|
-
export function createMemorySessionStore(initialEntries = []) {
|
|
96
|
+
export function createMemorySessionStore(initialEntries = [], options = {}) {
|
|
97
97
|
const byId = new Map();
|
|
98
98
|
const bySession = new Map();
|
|
99
99
|
const leafBySession = new Map();
|
|
100
100
|
const idempotencySeen = new Set();
|
|
101
|
+
const mode = options.sessionSearchMode ?? "linear";
|
|
101
102
|
for (const entry of initialEntries)
|
|
102
103
|
add(entry);
|
|
103
104
|
return {
|
|
104
|
-
async append(entry,
|
|
105
|
-
add(entry,
|
|
105
|
+
async append(entry, appendOptions) {
|
|
106
|
+
add(entry, appendOptions);
|
|
106
107
|
},
|
|
107
108
|
async list(sessionId) {
|
|
108
109
|
return (bySession.get(sessionId) ?? []).map(cloneEntry);
|
|
@@ -111,6 +112,11 @@ export function createMemorySessionStore(initialEntries = []) {
|
|
|
111
112
|
const entry = byId.get(id);
|
|
112
113
|
return entry ? cloneEntry(entry) : undefined;
|
|
113
114
|
},
|
|
115
|
+
async searchSessions(query) {
|
|
116
|
+
if (mode === "unsupported")
|
|
117
|
+
throw new SessionSearchUnsupportedError();
|
|
118
|
+
return searchMemorySessionsLinear(bySession, leafBySession, query);
|
|
119
|
+
},
|
|
114
120
|
};
|
|
115
121
|
function add(entry, options) {
|
|
116
122
|
// ponytail: idempotency dedup keyed on (session, key, expectedParentId) so a
|
|
@@ -146,6 +152,170 @@ export function createMemorySessionStore(initialEntries = []) {
|
|
|
146
152
|
leafBySession.set(entry.sessionId, entry.id);
|
|
147
153
|
}
|
|
148
154
|
}
|
|
155
|
+
function searchMemorySessionsLinear(bySession, leafBySession, query) {
|
|
156
|
+
const q = resolveSessionSearchQuery(query);
|
|
157
|
+
q.signal?.throwIfAborted();
|
|
158
|
+
let sessionsScanned = 0;
|
|
159
|
+
let entriesScanned = 0;
|
|
160
|
+
let bytesScanned = 0;
|
|
161
|
+
const matches = [];
|
|
162
|
+
for (const [sessionId, entries] of bySession) {
|
|
163
|
+
if (sessionsScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_SESSIONS)
|
|
164
|
+
break;
|
|
165
|
+
if (entriesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES)
|
|
166
|
+
break;
|
|
167
|
+
if (bytesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES)
|
|
168
|
+
break;
|
|
169
|
+
q.signal?.throwIfAborted();
|
|
170
|
+
sessionsScanned += 1;
|
|
171
|
+
let updatedAt = "";
|
|
172
|
+
let label;
|
|
173
|
+
let summary;
|
|
174
|
+
let workspaceRoot;
|
|
175
|
+
let tenantId;
|
|
176
|
+
let accountId;
|
|
177
|
+
let userId;
|
|
178
|
+
let matchedLabel = false;
|
|
179
|
+
let matchedSummary = false;
|
|
180
|
+
let matchedQuery = false;
|
|
181
|
+
let matchedProvider = false;
|
|
182
|
+
let matchedModel = false;
|
|
183
|
+
let snippetSource;
|
|
184
|
+
for (const entry of entries) {
|
|
185
|
+
if (entriesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_ENTRIES)
|
|
186
|
+
break;
|
|
187
|
+
if (bytesScanned >= DEFAULT_MAX_SESSION_SEARCH_LINEAR_BYTES)
|
|
188
|
+
break;
|
|
189
|
+
entriesScanned += 1;
|
|
190
|
+
const text = entrySearchText(entry);
|
|
191
|
+
bytesScanned += utf8Bytes(text) + utf8Bytes(entry.label) + utf8Bytes(entry.summary);
|
|
192
|
+
if (entry.timestamp > updatedAt)
|
|
193
|
+
updatedAt = entry.timestamp;
|
|
194
|
+
if (entry.label)
|
|
195
|
+
label = entry.label;
|
|
196
|
+
if (entry.summary)
|
|
197
|
+
summary = entry.summary;
|
|
198
|
+
const meta = entry.metadata;
|
|
199
|
+
if (meta) {
|
|
200
|
+
if (typeof meta[SESSION_SEARCH_WORKSPACE_METADATA_KEY] === "string") {
|
|
201
|
+
workspaceRoot = meta[SESSION_SEARCH_WORKSPACE_METADATA_KEY];
|
|
202
|
+
}
|
|
203
|
+
if (typeof meta.tenantId === "string")
|
|
204
|
+
tenantId = meta.tenantId;
|
|
205
|
+
if (typeof meta.accountId === "string")
|
|
206
|
+
accountId = meta.accountId;
|
|
207
|
+
if (typeof meta.userId === "string")
|
|
208
|
+
userId = meta.userId;
|
|
209
|
+
}
|
|
210
|
+
if (q.label && entry.label?.includes(q.label))
|
|
211
|
+
matchedLabel = true;
|
|
212
|
+
if (q.summary && entry.summary?.includes(q.summary))
|
|
213
|
+
matchedSummary = true;
|
|
214
|
+
if (q.query) {
|
|
215
|
+
const hay = `${entry.label ?? ""}\n${entry.summary ?? ""}\n${text}`;
|
|
216
|
+
if (hay.includes(q.query)) {
|
|
217
|
+
matchedQuery = true;
|
|
218
|
+
snippetSource ??= entry.label ?? entry.summary ?? text;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (q.provider && (entry.model?.provider === q.provider || metaProvider(entry) === q.provider))
|
|
222
|
+
matchedProvider = true;
|
|
223
|
+
if (q.model && entry.model?.model === q.model)
|
|
224
|
+
matchedModel = true;
|
|
225
|
+
}
|
|
226
|
+
if (q.workspaceRoot && workspaceRoot !== q.workspaceRoot)
|
|
227
|
+
continue;
|
|
228
|
+
if (q.tenantId && tenantId !== q.tenantId)
|
|
229
|
+
continue;
|
|
230
|
+
if (q.accountId && accountId !== q.accountId)
|
|
231
|
+
continue;
|
|
232
|
+
if (q.userId && userId !== q.userId)
|
|
233
|
+
continue;
|
|
234
|
+
if (q.label && !matchedLabel)
|
|
235
|
+
continue;
|
|
236
|
+
if (q.summary && !matchedSummary)
|
|
237
|
+
continue;
|
|
238
|
+
if (q.query && !matchedQuery)
|
|
239
|
+
continue;
|
|
240
|
+
if (q.provider && !matchedProvider)
|
|
241
|
+
continue;
|
|
242
|
+
if (q.model && !matchedModel)
|
|
243
|
+
continue;
|
|
244
|
+
if (q.fromUpdatedAt && updatedAt < q.fromUpdatedAt)
|
|
245
|
+
continue;
|
|
246
|
+
if (q.toUpdatedAt && updatedAt > q.toUpdatedAt)
|
|
247
|
+
continue;
|
|
248
|
+
matches.push({
|
|
249
|
+
sessionId,
|
|
250
|
+
leafId: leafBySession.get(sessionId),
|
|
251
|
+
updatedAt: updatedAt || undefined,
|
|
252
|
+
label,
|
|
253
|
+
summary,
|
|
254
|
+
snippet: clipSnippet(snippetSource ?? label ?? summary),
|
|
255
|
+
metadata: workspaceRoot !== undefined ? { [SESSION_SEARCH_WORKSPACE_METADATA_KEY]: workspaceRoot } : undefined,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
matches.sort((a, b) => compareSearchHit(a, b, q.order));
|
|
259
|
+
const afterCursor = q.cursor ? decodeSearchCursor(q.cursor) : undefined;
|
|
260
|
+
const filtered = afterCursor
|
|
261
|
+
? matches.filter((hit) => isAfterSearchCursor(hit, afterCursor, q.order))
|
|
262
|
+
: matches;
|
|
263
|
+
const page = filtered.slice(0, q.limit);
|
|
264
|
+
const last = page.at(-1);
|
|
265
|
+
return {
|
|
266
|
+
items: page,
|
|
267
|
+
nextCursor: filtered.length > q.limit && last?.updatedAt
|
|
268
|
+
? encodeSearchCursor(last.updatedAt, last.sessionId)
|
|
269
|
+
: undefined,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function entrySearchText(entry) {
|
|
273
|
+
if (!entry.message?.content)
|
|
274
|
+
return "";
|
|
275
|
+
const parts = [];
|
|
276
|
+
for (const block of entry.message.content) {
|
|
277
|
+
if (block.type === "text" && typeof block.text === "string")
|
|
278
|
+
parts.push(block.text);
|
|
279
|
+
}
|
|
280
|
+
return parts.join("\n");
|
|
281
|
+
}
|
|
282
|
+
function metaProvider(entry) {
|
|
283
|
+
const value = entry.metadata?.provider;
|
|
284
|
+
return typeof value === "string" ? value : undefined;
|
|
285
|
+
}
|
|
286
|
+
function utf8Bytes(value) {
|
|
287
|
+
return value ? new TextEncoder().encode(value).byteLength : 0;
|
|
288
|
+
}
|
|
289
|
+
function clipSnippet(value) {
|
|
290
|
+
if (!value)
|
|
291
|
+
return undefined;
|
|
292
|
+
const encoded = new TextEncoder().encode(value);
|
|
293
|
+
if (encoded.byteLength <= DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES)
|
|
294
|
+
return value;
|
|
295
|
+
return new TextDecoder().decode(encoded.slice(0, DEFAULT_MAX_SESSION_SEARCH_SNIPPET_BYTES));
|
|
296
|
+
}
|
|
297
|
+
function encodeSearchCursor(updatedAt, sessionId) {
|
|
298
|
+
return `${updatedAt}\t${sessionId}`;
|
|
299
|
+
}
|
|
300
|
+
function decodeSearchCursor(cursor) {
|
|
301
|
+
const tab = cursor.indexOf("\t");
|
|
302
|
+
if (tab <= 0 || tab === cursor.length - 1)
|
|
303
|
+
throw new TypeError("SessionSearchQuery.cursor is invalid");
|
|
304
|
+
return { updatedAt: cursor.slice(0, tab), sessionId: cursor.slice(tab + 1) };
|
|
305
|
+
}
|
|
306
|
+
function compareSearchHit(a, b, order) {
|
|
307
|
+
const aAt = a.updatedAt ?? "";
|
|
308
|
+
const bAt = b.updatedAt ?? "";
|
|
309
|
+
const cmp = aAt < bAt ? -1 : aAt > bAt ? 1 : a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0;
|
|
310
|
+
return order === "asc" ? cmp : -cmp;
|
|
311
|
+
}
|
|
312
|
+
function isAfterSearchCursor(hit, cursor, order) {
|
|
313
|
+
const at = hit.updatedAt ?? "";
|
|
314
|
+
if (order === "asc") {
|
|
315
|
+
return at > cursor.updatedAt || (at === cursor.updatedAt && hit.sessionId > cursor.sessionId);
|
|
316
|
+
}
|
|
317
|
+
return at < cursor.updatedAt || (at === cursor.updatedAt && hit.sessionId < cursor.sessionId);
|
|
318
|
+
}
|
|
149
319
|
function cloneEntry(entry) {
|
|
150
320
|
return structuredClone(entry);
|
|
151
321
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentConfig, ModelCapabilities, ModelConfig, ProviderRequestOptions, RunOptions, StructuredOutputOptions } from "./contracts.js";
|
|
1
|
+
import type { AgentConfig, ModelCapabilities, ModelConfig, ProviderRequest, ProviderRequestOptions, RunOptions, StructuredOutputOptions } from "./contracts.js";
|
|
2
2
|
export declare const DEFAULT_MAX_STRUCTURED_OUTPUT_SCHEMA_BYTES = 65536;
|
|
3
3
|
export declare const DEFAULT_MAX_STRUCTURED_OUTPUT_NAME_LENGTH = 128;
|
|
4
4
|
export declare class StructuredOutputError extends Error {
|
|
@@ -9,3 +9,7 @@ export declare function modelSupportsStructuredOutput(capabilities?: ModelCapabi
|
|
|
9
9
|
export declare function validateStructuredOutputOptions(options: StructuredOutputOptions): StructuredOutputOptions;
|
|
10
10
|
export declare function assertStructuredOutputRequestSupported(model: ModelConfig, options?: ProviderRequestOptions): void;
|
|
11
11
|
export declare function resolveRunProviderOptions(runOptions: Pick<RunOptions, "providerOptions" | "loop">, config: Pick<AgentConfig, "providerOptions" | "loop">): ProviderRequestOptions | undefined;
|
|
12
|
+
/** Strip native schema so a tool-eligible turn can call tools freely. */
|
|
13
|
+
export declare function withoutStructuredOutput(request: ProviderRequest): ProviderRequest;
|
|
14
|
+
/** Artifact/revision turn: keep/restore schema and withdraw tools. */
|
|
15
|
+
export declare function artifactStructuredOutputRequest(request: ProviderRequest, schema?: StructuredOutputOptions): ProviderRequest;
|
|
@@ -56,4 +56,22 @@ export function resolveRunProviderOptions(runOptions, config) {
|
|
|
56
56
|
structuredOutput: validateStructuredOutputOptions(loop.structuredOutput),
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
|
+
/** Strip native schema so a tool-eligible turn can call tools freely. */
|
|
60
|
+
export function withoutStructuredOutput(request) {
|
|
61
|
+
if (!request.options?.structuredOutput)
|
|
62
|
+
return request;
|
|
63
|
+
const { structuredOutput: _drop, ...options } = request.options;
|
|
64
|
+
return { ...request, options: Object.keys(options).length > 0 ? options : undefined };
|
|
65
|
+
}
|
|
66
|
+
/** Artifact/revision turn: keep/restore schema and withdraw tools. */
|
|
67
|
+
export function artifactStructuredOutputRequest(request, schema) {
|
|
68
|
+
const structuredOutput = schema ?? request.options?.structuredOutput;
|
|
69
|
+
if (!structuredOutput)
|
|
70
|
+
return { ...request, tools: undefined };
|
|
71
|
+
return {
|
|
72
|
+
...request,
|
|
73
|
+
tools: undefined,
|
|
74
|
+
options: { ...request.options, structuredOutput },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
59
77
|
//# sourceMappingURL=structured-output.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PersistencePage, SessionEntry, SessionEntryQuery } from "../contracts.js";
|
|
2
2
|
/** Current shared persistence schema version for production database adapters. */
|
|
3
|
-
export declare const PERSISTENCE_SCHEMA_VERSION =
|
|
3
|
+
export declare const PERSISTENCE_SCHEMA_VERSION = 4;
|
|
4
4
|
export type PersistenceTableName = "prism_tenants" | "prism_accounts" | "prism_users" | "prism_agent_definitions" | "prism_sessions" | "prism_branches" | "prism_session_entries" | "prism_session_append_idempotency" | "prism_runs" | "prism_agent_events" | "prism_tool_calls" | "prism_usage" | "prism_run_feedback" | "prism_retention_policies" | "prism_migrations";
|
|
5
5
|
export type PersistenceColumnType = "text" | "integer" | "number" | "boolean" | "json" | "timestamp";
|
|
6
6
|
export interface PersistenceColumnDefinition {
|