@xandreed/core 0.4.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/conformance.entity.d.ts +15 -0
- package/dist/conformance.entity.js +7 -0
- package/dist/decision-record.entity.d.ts +23 -23
- package/dist/decision-record.entity.functions.d.ts +6 -0
- package/dist/decision-record.entity.functions.js +3 -0
- package/dist/decision-record.entity.js +4 -4
- package/dist/domain/agent-failure.entity.d.ts +6 -6
- package/dist/domain/agent-failure.entity.js +12 -1
- package/dist/domain/failure.entity.d.ts +2 -2
- package/dist/domain/loop-event.entity.d.ts +2 -0
- package/dist/domain/message.entity.d.ts +143 -156
- package/dist/domain/message.entity.js +6 -6
- package/dist/domain/model-call-policy.entity.d.ts +3 -3
- package/dist/domain/model-call-policy.entity.js +1 -1
- package/dist/domain/model-catalog.entity.d.ts +4 -4
- package/dist/domain/model-catalog.entity.js +1 -1
- package/dist/domain/model-selection.entity.d.ts +6 -13
- package/dist/domain/prompt-provenance.entity.d.ts +6 -6
- package/dist/domain/prompt-provenance.entity.js +4 -4
- package/dist/domain/token-usage.entity.d.ts +4 -4
- package/dist/harness/capability.entity.d.ts +88 -41
- package/dist/harness/capability.entity.functions.d.ts +30 -1
- package/dist/harness/capability.entity.functions.js +55 -2
- package/dist/harness/capability.entity.js +33 -12
- package/dist/harness/config.entity.d.ts +28 -28
- package/dist/harness/config.entity.js +4 -4
- package/dist/harness/plugin-stack.adapter.d.ts +24 -0
- package/dist/harness/plugin-stack.adapter.js +8 -0
- package/dist/harness/plugin-stack.adapter.test.d.ts +1 -0
- package/dist/harness/plugin.adapter.d.ts +15 -4
- package/dist/harness/plugin.adapter.js +18 -11
- package/dist/harness/plugin.adapter.test.d.ts +1 -0
- package/dist/harness/plugin.entity.d.ts +26 -10
- package/dist/harness/plugin.entity.js +1 -1
- package/dist/harness/session.entity.d.ts +20 -20
- package/dist/harness/session.entity.js +1 -1
- package/dist/harness/skill-files.d.ts +17 -0
- package/dist/harness/skill-files.js +25 -0
- package/dist/index.d.ts +47 -4
- package/dist/index.js +45 -4
- package/dist/loop/cacheKey.d.ts +3 -3
- package/dist/loop/cacheKey.js +4 -2
- package/dist/loop/mapping.d.ts +4 -5
- package/dist/loop/mapping.js +8 -16
- package/dist/loop/modelPolicy.d.ts +4 -4
- package/dist/loop/modelPolicy.js +2 -2
- package/dist/loop/promptProvenance.d.ts +2 -2
- package/dist/loop/promptProvenance.js +2 -2
- package/dist/loop/stepContext.d.ts +2 -2
- package/dist/loop/stepContext.js +2 -2
- package/dist/loop/streamFold.js +3 -3
- package/dist/loop/toolSchema.d.ts +17 -0
- package/dist/loop/toolSchema.js +25 -0
- package/dist/loop/toolSchema.test.d.ts +1 -0
- package/dist/mcp/bridge.d.ts +15 -15
- package/dist/mcp/bridge.js +14 -14
- package/dist/memory/memory-log.entity.d.ts +320 -0
- package/dist/memory/memory-log.entity.functions.d.ts +89 -0
- package/dist/memory/memory-log.entity.functions.js +180 -0
- package/dist/memory/memory-log.entity.js +75 -0
- package/dist/memory/memory-log.entity.test.d.ts +1 -0
- package/dist/memory/memory-session.d.ts +51 -0
- package/dist/memory/memory-session.js +97 -0
- package/dist/memory/memory-session.test.d.ts +1 -0
- package/dist/memory/memory.conformance.d.ts +32 -0
- package/dist/memory/memory.conformance.js +102 -0
- package/dist/ports/auth-store.port.d.ts +16 -18
- package/dist/ports/auth-store.port.js +13 -9
- package/dist/ports/capability.port.d.ts +131 -7
- package/dist/ports/capability.port.js +2 -2
- package/dist/ports/conversation-store.port.d.ts +54 -177
- package/dist/ports/conversation-store.port.js +9 -9
- package/dist/ports/file-system.port.d.ts +5 -7
- package/dist/ports/file-system.port.js +1 -1
- package/dist/ports/harness.port.d.ts +19 -13
- package/dist/ports/harness.port.js +10 -10
- package/dist/ports/mcp-client.port.d.ts +15 -47
- package/dist/ports/mcp-client.port.js +3 -3
- package/dist/ports/memory.port.d.ts +116 -0
- package/dist/ports/memory.port.js +7 -0
- package/dist/ports/model-catalog.port.d.ts +1 -1
- package/dist/ports/model-catalog.port.js +1 -1
- package/dist/ports/permission.port.d.ts +10 -0
- package/dist/ports/permission.port.js +4 -0
- package/dist/ports/run-context.port.d.ts +28 -0
- package/dist/ports/run-context.port.js +4 -0
- package/dist/ports/session-log.port.d.ts +25 -0
- package/dist/ports/session-log.port.js +4 -0
- package/dist/ports/sessions.port.d.ts +141 -0
- package/dist/ports/sessions.port.js +4 -0
- package/dist/ports/settings-store.port.d.ts +18 -56
- package/dist/ports/settings-store.port.js +3 -3
- package/dist/ports/shell.port.d.ts +9 -21
- package/dist/ports/shell.port.js +1 -1
- package/dist/ports/step-loop.port.d.ts +85 -0
- package/dist/ports/step-loop.port.js +4 -0
- package/dist/ports/tasks.port.d.ts +59 -0
- package/dist/ports/tasks.port.js +10 -0
- package/dist/ports/tool-registry.port.d.ts +53 -0
- package/dist/ports/tool-registry.port.js +4 -0
- package/dist/ports/turn-admission.port.d.ts +23 -0
- package/dist/ports/turn-admission.port.js +4 -0
- package/dist/ports/turn-events.port.d.ts +55 -0
- package/dist/ports/turn-events.port.js +7 -0
- package/dist/ports/turn-scope.port.d.ts +101 -0
- package/dist/ports/turn-scope.port.js +10 -0
- package/dist/ports/turn.port.d.ts +113 -0
- package/dist/ports/turn.port.js +0 -0
- package/dist/ports/utility-llm.port.d.ts +12 -31
- package/dist/ports/utility-llm.port.js +1 -1
- package/dist/session/chassis.js +7 -7
- package/dist/session/session-event.entity.d.ts +55 -0
- package/dist/session/session-event.entity.functions.d.ts +31 -0
- package/dist/session/session-event.entity.functions.js +42 -0
- package/dist/session/session-event.entity.js +37 -0
- package/dist/session/session-event.entity.test.d.ts +1 -0
- package/dist/session/session-log.conformance.d.ts +13 -0
- package/dist/session/session-log.conformance.js +167 -0
- package/dist/session/session-log.entity.d.ts +164 -0
- package/dist/session/session-log.entity.js +82 -0
- package/dist/session/session-log.memory.adapter.d.ts +7 -0
- package/dist/session/session-log.memory.adapter.js +82 -0
- package/dist/session/session-log.memory.adapter.test.d.ts +1 -0
- package/dist/session/sessions.entity.d.ts +201 -0
- package/dist/session/sessions.entity.js +98 -0
- package/dist/session/tasks.entity.d.ts +39 -0
- package/dist/session/tasks.entity.js +26 -0
- package/dist/session/turn-admission.open.adapter.d.ts +4 -0
- package/dist/session/turn-admission.open.adapter.js +3 -0
- package/dist/spec/SpecDoc.d.ts +30 -117
- package/dist/spec/SpecDoc.js +10 -10
- package/dist/spec/codec.js +1 -1
- package/dist/turn/model-request.entity.d.ts +99 -0
- package/dist/turn/model-request.entity.functions.d.ts +108 -0
- package/dist/turn/model-request.entity.functions.js +167 -0
- package/dist/turn/model-request.entity.js +41 -0
- package/dist/turn/model-request.entity.test.d.ts +1 -0
- package/dist/turn/prompt-sections.d.ts +10 -0
- package/dist/turn/prompt-sections.js +3 -0
- package/dist/turn/step-loop.conformance.d.ts +16 -0
- package/dist/turn/step-loop.conformance.js +164 -0
- package/dist/turn/testing.d.ts +30 -0
- package/dist/turn/turn-bus.d.ts +23 -0
- package/dist/turn/turn-bus.js +54 -0
- package/dist/turn/turn-bus.test.d.ts +1 -0
- package/dist/turn/turn-event.entity.d.ts +263 -0
- package/dist/turn/turn-event.entity.functions.d.ts +35 -0
- package/dist/turn/turn-event.entity.functions.js +21 -0
- package/dist/turn/turn-event.entity.js +101 -0
- package/dist/turn/turn-lifecycle.d.ts +32 -0
- package/dist/turn/turn-lifecycle.js +48 -0
- package/dist/turn/turn-run.d.ts +26 -0
- package/dist/turn/turn-run.js +105 -0
- package/dist/turn/turn.adapter.d.ts +21 -0
- package/dist/turn/turn.adapter.js +126 -0
- package/dist/turn/turn.adapter.test.d.ts +1 -0
- package/dist/turn/turn.conformance.d.ts +20 -0
- package/dist/turn/turn.conformance.js +74 -0
- package/dist/turn/user-message.entity.d.ts +14 -0
- package/dist/turn/user-message.entity.js +10 -0
- package/dist/util/json.d.ts +2 -2
- package/dist/util/json.js +10 -10
- package/package.json +3 -3
package/dist/mcp/bridge.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { Tool, Toolkit } from "
|
|
1
|
+
import { Tool, Toolkit } from "effect/ai";
|
|
2
2
|
import { Context, Effect, Option, Schema } from "effect";
|
|
3
3
|
import { Failure } from "../domain/failure.entity.js";
|
|
4
4
|
import { McpClient } from "../ports/mcp-client.port.js";
|
|
5
|
-
const OUTPUT_CAP_CHARS = 16000, McpArgs = Schema.Record(
|
|
5
|
+
const OUTPUT_CAP_CHARS = 16000, McpArgs = Schema.Record(Schema.String, Schema.Unknown), clipResult = (value) => {
|
|
6
6
|
const text = typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
7
7
|
return text.length <= OUTPUT_CAP_CHARS ? value : `${text.slice(0, OUTPUT_CAP_CHARS)}
|
|
8
8
|
[\u2026clipped ${text.length - OUTPUT_CAP_CHARS} chars\u2026]`;
|
|
9
9
|
};
|
|
10
10
|
export const McpDescribe = Tool.make("mcp_describe", {
|
|
11
11
|
description: "Reveal ONE external MCP tool's parameters. Pass the server + tool exactly as listed under 'External MCP tools'. Returns {server, tool, description, inputSchema} \u2014 the JSON Schema of the arguments mcp_call expects. Call this BEFORE mcp_call the first time you use a tool.",
|
|
12
|
-
parameters: {
|
|
13
|
-
server: Schema.String.
|
|
14
|
-
tool: Schema.String.
|
|
15
|
-
},
|
|
12
|
+
parameters: Schema.Struct({
|
|
13
|
+
server: Schema.String.annotate({ description: "The server name, as listed." }),
|
|
14
|
+
tool: Schema.String.annotate({ description: "The tool name, as listed." })
|
|
15
|
+
}),
|
|
16
16
|
success: Schema.Struct({
|
|
17
17
|
server: Schema.String,
|
|
18
18
|
tool: Schema.String,
|
|
@@ -23,13 +23,13 @@ export const McpDescribe = Tool.make("mcp_describe", {
|
|
|
23
23
|
failureMode: "return"
|
|
24
24
|
}), McpCall = Tool.make("mcp_call", {
|
|
25
25
|
description: "Invoke an external MCP tool. Pass server + tool (as listed) and args as a JSON object matching the schema mcp_describe returned. A tool-level failure comes back as data (read it and adapt), not a dead turn. Returns the tool's result (clipped past 16k chars).",
|
|
26
|
-
parameters: {
|
|
27
|
-
server: Schema.String.
|
|
28
|
-
tool: Schema.String.
|
|
29
|
-
args: McpArgs.
|
|
26
|
+
parameters: Schema.Struct({
|
|
27
|
+
server: Schema.String.annotate({ description: "The server name, as listed." }),
|
|
28
|
+
tool: Schema.String.annotate({ description: "The tool name, as listed." }),
|
|
29
|
+
args: McpArgs.annotate({
|
|
30
30
|
description: "The tool's arguments as a JSON object (see mcp_describe); {} if it takes none."
|
|
31
31
|
})
|
|
32
|
-
},
|
|
32
|
+
}),
|
|
33
33
|
success: Schema.Unknown,
|
|
34
34
|
failure: Failure,
|
|
35
35
|
failureMode: "return"
|
|
@@ -41,7 +41,7 @@ export const McpDescribe = Tool.make("mcp_describe", {
|
|
|
41
41
|
const client = yield* McpClient, descriptors = yield* client.listTools;
|
|
42
42
|
if (descriptors.length === 0)
|
|
43
43
|
return emptyMcpBridge;
|
|
44
|
-
const find = (server, tool) => Option.
|
|
44
|
+
const find = (server, tool) => Option.fromNullishOr(descriptors.find((d) => d.server === server && d.name === tool)), tools = [McpDescribe, McpCall], toolkit = Toolkit.make(...tools), handlerRecord = {
|
|
45
45
|
mcp_describe: (params) => Option.match(find(params.server, params.tool), {
|
|
46
46
|
onNone: () => Effect.fail({
|
|
47
47
|
error: "UnknownMcpTool",
|
|
@@ -61,10 +61,10 @@ export const McpDescribe = Tool.make("mcp_describe", {
|
|
|
61
61
|
error: "McpToolError",
|
|
62
62
|
message: typeof outcome.result === "string" ? outcome.result.slice(0, 2000) : JSON.stringify(outcome.result).slice(0, 2000)
|
|
63
63
|
}) : Effect.succeed(clipResult(outcome.result))), Effect.catchTag("McpError", (error) => Effect.fail({ error: "McpTransportError", message: error.message })))
|
|
64
|
-
}, handlers = yield* toolkit.
|
|
64
|
+
}, handlers = yield* toolkit.toHandlers(handlerRecord);
|
|
65
65
|
return {
|
|
66
66
|
toolkit,
|
|
67
67
|
handlers,
|
|
68
68
|
descriptors
|
|
69
69
|
};
|
|
70
|
-
}).pipe(Effect.
|
|
70
|
+
}).pipe(Effect.catchCause((cause) => Effect.logWarning(`MCP bridge unavailable \u2014 continuing without MCP tools: ${cause}`).pipe(Effect.as(emptyMcpBridge))));
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
/**
|
|
3
|
+
* The conversation memory log — the single source every model request is
|
|
4
|
+
* rebuilt from ("model-visible ⟺ logged"). Entries are appended, never edited:
|
|
5
|
+
* a compaction is itself an entry that carries the replacement bytes, so a
|
|
6
|
+
* replay reproduces exactly what the model saw.
|
|
7
|
+
*/
|
|
8
|
+
/** `<runId>:<index within the run>` — assigned by memory, independent of any journal. */
|
|
9
|
+
export declare const EntryId: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
10
|
+
export type EntryId = typeof EntryId.Type;
|
|
11
|
+
/** Something a tool result discovered, e.g. a record the next turn may cite. */
|
|
12
|
+
export declare const Subject: Schema.Struct<{
|
|
13
|
+
readonly kind: Schema.Trimmed;
|
|
14
|
+
readonly id: Schema.Trimmed;
|
|
15
|
+
readonly label: Schema.OptionFromNullOr<Schema.String>;
|
|
16
|
+
readonly data: Schema.OptionFromNullOr<Schema.Unknown>;
|
|
17
|
+
}>;
|
|
18
|
+
export type Subject = typeof Subject.Type;
|
|
19
|
+
/** A file or image a tool result carries, kept by reference (never inlined in the log). */
|
|
20
|
+
export declare const ArtifactRef: Schema.Struct<{
|
|
21
|
+
readonly id: Schema.Trimmed;
|
|
22
|
+
readonly kind: Schema.Literals<readonly ["image", "file"]>;
|
|
23
|
+
readonly mediaType: Schema.Trimmed;
|
|
24
|
+
readonly url: Schema.String;
|
|
25
|
+
readonly alt: Schema.OptionFromNullOr<Schema.String>;
|
|
26
|
+
}>;
|
|
27
|
+
export type ArtifactRef = typeof ArtifactRef.Type;
|
|
28
|
+
export declare const CompactionAction: Schema.Union<readonly [Schema.TaggedStruct<"CompactViews", {
|
|
29
|
+
readonly entries: Schema.$Array<Schema.brand<Schema.Trimmed, "EntryId">>;
|
|
30
|
+
readonly texts: Schema.$Array<Schema.String>;
|
|
31
|
+
}>, Schema.TaggedStruct<"Spill", {
|
|
32
|
+
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
33
|
+
readonly preview: Schema.String;
|
|
34
|
+
}>, Schema.TaggedStruct<"DropTurns", {
|
|
35
|
+
readonly throughTurn: Schema.Int;
|
|
36
|
+
readonly ledger: Schema.String;
|
|
37
|
+
}>, Schema.TaggedStruct<"Summarize", {
|
|
38
|
+
readonly keepFromTurn: Schema.Int;
|
|
39
|
+
readonly summary: Schema.String;
|
|
40
|
+
}>]>;
|
|
41
|
+
export type CompactionAction = typeof CompactionAction.Type;
|
|
42
|
+
export declare const ActivationSource: Schema.Literals<readonly ["always", "matcher", "load_skill", "host"]>;
|
|
43
|
+
export type ActivationSource = typeof ActivationSource.Type;
|
|
44
|
+
export declare const LogBody: Schema.Union<readonly [Schema.TaggedStruct<"SystemPrepared", {
|
|
45
|
+
readonly fingerprint: Schema.String;
|
|
46
|
+
readonly text: Schema.String;
|
|
47
|
+
readonly sections: Schema.$Array<Schema.Struct<{
|
|
48
|
+
readonly id: Schema.String;
|
|
49
|
+
readonly version: Schema.String;
|
|
50
|
+
readonly fingerprint: Schema.String;
|
|
51
|
+
}>>;
|
|
52
|
+
}>, Schema.encodeKeys<Schema.TaggedStruct<"TurnStarted", {
|
|
53
|
+
readonly userMessage: Schema.decodeTo<typeof import("@xandreed/sdk").UserMessage, Schema.String, never, never>;
|
|
54
|
+
}>, {
|
|
55
|
+
readonly userMessage: "prompt";
|
|
56
|
+
}>, Schema.TaggedStruct<"TurnContext", {
|
|
57
|
+
readonly sectionId: Schema.String;
|
|
58
|
+
readonly version: Schema.String;
|
|
59
|
+
readonly text: Schema.String;
|
|
60
|
+
}>, Schema.TaggedStruct<"Message", {
|
|
61
|
+
readonly message: Schema.Union<readonly [Schema.Struct<{
|
|
62
|
+
readonly role: Schema.Literal<"user">;
|
|
63
|
+
readonly content: Schema.String;
|
|
64
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
65
|
+
}>, Schema.Struct<{
|
|
66
|
+
readonly role: Schema.Literal<"assistant">;
|
|
67
|
+
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
68
|
+
readonly type: Schema.Literal<"text">;
|
|
69
|
+
readonly text: Schema.String;
|
|
70
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
71
|
+
}>, Schema.Struct<{
|
|
72
|
+
readonly type: Schema.Literal<"reasoning">;
|
|
73
|
+
readonly text: Schema.String;
|
|
74
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
75
|
+
}>, Schema.Struct<{
|
|
76
|
+
readonly type: Schema.Literal<"tool-call">;
|
|
77
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
78
|
+
readonly toolName: Schema.String;
|
|
79
|
+
readonly input: Schema.Unknown;
|
|
80
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
81
|
+
readonly providerExecuted: Schema.optional<Schema.Boolean>;
|
|
82
|
+
}>]>>;
|
|
83
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
84
|
+
}>, Schema.Struct<{
|
|
85
|
+
readonly role: Schema.Literal<"tool">;
|
|
86
|
+
readonly content: Schema.$Array<Schema.Struct<{
|
|
87
|
+
readonly type: Schema.Literal<"tool-result">;
|
|
88
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
89
|
+
readonly toolName: Schema.String;
|
|
90
|
+
readonly output: Schema.Unknown;
|
|
91
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
92
|
+
readonly isError: Schema.optional<Schema.Boolean>;
|
|
93
|
+
}>>;
|
|
94
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
95
|
+
}>]>;
|
|
96
|
+
}>, Schema.TaggedStruct<"ToolResult", {
|
|
97
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
98
|
+
readonly toolName: Schema.String;
|
|
99
|
+
readonly isError: Schema.Boolean;
|
|
100
|
+
/** Full fidelity: the tool's encoded result. */
|
|
101
|
+
readonly encoded: Schema.Unknown;
|
|
102
|
+
/** The exact text the model saw when the result was written. */
|
|
103
|
+
readonly view: Schema.String;
|
|
104
|
+
readonly viewVersion: Schema.String;
|
|
105
|
+
readonly subjects: Schema.$Array<Schema.Struct<{
|
|
106
|
+
readonly kind: Schema.Trimmed;
|
|
107
|
+
readonly id: Schema.Trimmed;
|
|
108
|
+
readonly label: Schema.OptionFromNullOr<Schema.String>;
|
|
109
|
+
readonly data: Schema.OptionFromNullOr<Schema.Unknown>;
|
|
110
|
+
}>>;
|
|
111
|
+
readonly artifacts: Schema.$Array<Schema.Struct<{
|
|
112
|
+
readonly id: Schema.Trimmed;
|
|
113
|
+
readonly kind: Schema.Literals<readonly ["image", "file"]>;
|
|
114
|
+
readonly mediaType: Schema.Trimmed;
|
|
115
|
+
readonly url: Schema.String;
|
|
116
|
+
readonly alt: Schema.OptionFromNullOr<Schema.String>;
|
|
117
|
+
}>>;
|
|
118
|
+
/** Pinned results survive every compaction verbatim. */
|
|
119
|
+
readonly pinned: Schema.Boolean;
|
|
120
|
+
}>, Schema.TaggedStruct<"ToolDigest", {
|
|
121
|
+
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
122
|
+
readonly version: Schema.String;
|
|
123
|
+
readonly mode: Schema.Literals<readonly ["select", "summarize"]>;
|
|
124
|
+
readonly keep: Schema.$Array<Schema.String>;
|
|
125
|
+
readonly text: Schema.String;
|
|
126
|
+
readonly digester: Schema.String;
|
|
127
|
+
readonly trigger: Schema.Literals<readonly ["write", "compaction"]>;
|
|
128
|
+
}>, Schema.TaggedStruct<"StepContext", {
|
|
129
|
+
readonly step: Schema.Int;
|
|
130
|
+
readonly text: Schema.String;
|
|
131
|
+
}>, Schema.TaggedStruct<"ToolsActivated", {
|
|
132
|
+
readonly skills: Schema.$Array<Schema.String>;
|
|
133
|
+
readonly tools: Schema.$Array<Schema.String>;
|
|
134
|
+
readonly source: Schema.Literals<readonly ["always", "matcher", "load_skill", "host"]>;
|
|
135
|
+
readonly decision: Schema.OptionFromNullOr<Schema.brand<Schema.Trimmed, "DecisionId">>;
|
|
136
|
+
}>, Schema.TaggedStruct<"Compaction", {
|
|
137
|
+
readonly strategy: Schema.String;
|
|
138
|
+
readonly version: Schema.String;
|
|
139
|
+
readonly action: Schema.Union<readonly [Schema.TaggedStruct<"CompactViews", {
|
|
140
|
+
readonly entries: Schema.$Array<Schema.brand<Schema.Trimmed, "EntryId">>;
|
|
141
|
+
readonly texts: Schema.$Array<Schema.String>;
|
|
142
|
+
}>, Schema.TaggedStruct<"Spill", {
|
|
143
|
+
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
144
|
+
readonly preview: Schema.String;
|
|
145
|
+
}>, Schema.TaggedStruct<"DropTurns", {
|
|
146
|
+
readonly throughTurn: Schema.Int;
|
|
147
|
+
readonly ledger: Schema.String;
|
|
148
|
+
}>, Schema.TaggedStruct<"Summarize", {
|
|
149
|
+
readonly keepFromTurn: Schema.Int;
|
|
150
|
+
readonly summary: Schema.String;
|
|
151
|
+
}>]>;
|
|
152
|
+
}>, Schema.TaggedStruct<"TurnEnded", {
|
|
153
|
+
readonly outcome: Schema.Literals<readonly ["completed", "partial", "failed"]>;
|
|
154
|
+
readonly reply: Schema.OptionFromNullOr<Schema.String>;
|
|
155
|
+
}>]>;
|
|
156
|
+
export type LogBody = typeof LogBody.Type;
|
|
157
|
+
export declare const LogEntry: Schema.Struct<{
|
|
158
|
+
readonly id: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
159
|
+
readonly runId: Schema.String;
|
|
160
|
+
readonly turn: Schema.Int;
|
|
161
|
+
readonly step: Schema.Int;
|
|
162
|
+
readonly at: Schema.Number;
|
|
163
|
+
readonly body: Schema.Union<readonly [Schema.TaggedStruct<"SystemPrepared", {
|
|
164
|
+
readonly fingerprint: Schema.String;
|
|
165
|
+
readonly text: Schema.String;
|
|
166
|
+
readonly sections: Schema.$Array<Schema.Struct<{
|
|
167
|
+
readonly id: Schema.String;
|
|
168
|
+
readonly version: Schema.String;
|
|
169
|
+
readonly fingerprint: Schema.String;
|
|
170
|
+
}>>;
|
|
171
|
+
}>, Schema.encodeKeys<Schema.TaggedStruct<"TurnStarted", {
|
|
172
|
+
readonly userMessage: Schema.decodeTo<typeof import("@xandreed/sdk").UserMessage, Schema.String, never, never>;
|
|
173
|
+
}>, {
|
|
174
|
+
readonly userMessage: "prompt";
|
|
175
|
+
}>, Schema.TaggedStruct<"TurnContext", {
|
|
176
|
+
readonly sectionId: Schema.String;
|
|
177
|
+
readonly version: Schema.String;
|
|
178
|
+
readonly text: Schema.String;
|
|
179
|
+
}>, Schema.TaggedStruct<"Message", {
|
|
180
|
+
readonly message: Schema.Union<readonly [Schema.Struct<{
|
|
181
|
+
readonly role: Schema.Literal<"user">;
|
|
182
|
+
readonly content: Schema.String;
|
|
183
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
184
|
+
}>, Schema.Struct<{
|
|
185
|
+
readonly role: Schema.Literal<"assistant">;
|
|
186
|
+
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
187
|
+
readonly type: Schema.Literal<"text">;
|
|
188
|
+
readonly text: Schema.String;
|
|
189
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
190
|
+
}>, Schema.Struct<{
|
|
191
|
+
readonly type: Schema.Literal<"reasoning">;
|
|
192
|
+
readonly text: Schema.String;
|
|
193
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
194
|
+
}>, Schema.Struct<{
|
|
195
|
+
readonly type: Schema.Literal<"tool-call">;
|
|
196
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
197
|
+
readonly toolName: Schema.String;
|
|
198
|
+
readonly input: Schema.Unknown;
|
|
199
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
200
|
+
readonly providerExecuted: Schema.optional<Schema.Boolean>;
|
|
201
|
+
}>]>>;
|
|
202
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
203
|
+
}>, Schema.Struct<{
|
|
204
|
+
readonly role: Schema.Literal<"tool">;
|
|
205
|
+
readonly content: Schema.$Array<Schema.Struct<{
|
|
206
|
+
readonly type: Schema.Literal<"tool-result">;
|
|
207
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
208
|
+
readonly toolName: Schema.String;
|
|
209
|
+
readonly output: Schema.Unknown;
|
|
210
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
211
|
+
readonly isError: Schema.optional<Schema.Boolean>;
|
|
212
|
+
}>>;
|
|
213
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
214
|
+
}>]>;
|
|
215
|
+
}>, Schema.TaggedStruct<"ToolResult", {
|
|
216
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
217
|
+
readonly toolName: Schema.String;
|
|
218
|
+
readonly isError: Schema.Boolean;
|
|
219
|
+
/** Full fidelity: the tool's encoded result. */
|
|
220
|
+
readonly encoded: Schema.Unknown;
|
|
221
|
+
/** The exact text the model saw when the result was written. */
|
|
222
|
+
readonly view: Schema.String;
|
|
223
|
+
readonly viewVersion: Schema.String;
|
|
224
|
+
readonly subjects: Schema.$Array<Schema.Struct<{
|
|
225
|
+
readonly kind: Schema.Trimmed;
|
|
226
|
+
readonly id: Schema.Trimmed;
|
|
227
|
+
readonly label: Schema.OptionFromNullOr<Schema.String>;
|
|
228
|
+
readonly data: Schema.OptionFromNullOr<Schema.Unknown>;
|
|
229
|
+
}>>;
|
|
230
|
+
readonly artifacts: Schema.$Array<Schema.Struct<{
|
|
231
|
+
readonly id: Schema.Trimmed;
|
|
232
|
+
readonly kind: Schema.Literals<readonly ["image", "file"]>;
|
|
233
|
+
readonly mediaType: Schema.Trimmed;
|
|
234
|
+
readonly url: Schema.String;
|
|
235
|
+
readonly alt: Schema.OptionFromNullOr<Schema.String>;
|
|
236
|
+
}>>;
|
|
237
|
+
/** Pinned results survive every compaction verbatim. */
|
|
238
|
+
readonly pinned: Schema.Boolean;
|
|
239
|
+
}>, Schema.TaggedStruct<"ToolDigest", {
|
|
240
|
+
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
241
|
+
readonly version: Schema.String;
|
|
242
|
+
readonly mode: Schema.Literals<readonly ["select", "summarize"]>;
|
|
243
|
+
readonly keep: Schema.$Array<Schema.String>;
|
|
244
|
+
readonly text: Schema.String;
|
|
245
|
+
readonly digester: Schema.String;
|
|
246
|
+
readonly trigger: Schema.Literals<readonly ["write", "compaction"]>;
|
|
247
|
+
}>, Schema.TaggedStruct<"StepContext", {
|
|
248
|
+
readonly step: Schema.Int;
|
|
249
|
+
readonly text: Schema.String;
|
|
250
|
+
}>, Schema.TaggedStruct<"ToolsActivated", {
|
|
251
|
+
readonly skills: Schema.$Array<Schema.String>;
|
|
252
|
+
readonly tools: Schema.$Array<Schema.String>;
|
|
253
|
+
readonly source: Schema.Literals<readonly ["always", "matcher", "load_skill", "host"]>;
|
|
254
|
+
readonly decision: Schema.OptionFromNullOr<Schema.brand<Schema.Trimmed, "DecisionId">>;
|
|
255
|
+
}>, Schema.TaggedStruct<"Compaction", {
|
|
256
|
+
readonly strategy: Schema.String;
|
|
257
|
+
readonly version: Schema.String;
|
|
258
|
+
readonly action: Schema.Union<readonly [Schema.TaggedStruct<"CompactViews", {
|
|
259
|
+
readonly entries: Schema.$Array<Schema.brand<Schema.Trimmed, "EntryId">>;
|
|
260
|
+
readonly texts: Schema.$Array<Schema.String>;
|
|
261
|
+
}>, Schema.TaggedStruct<"Spill", {
|
|
262
|
+
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
263
|
+
readonly preview: Schema.String;
|
|
264
|
+
}>, Schema.TaggedStruct<"DropTurns", {
|
|
265
|
+
readonly throughTurn: Schema.Int;
|
|
266
|
+
readonly ledger: Schema.String;
|
|
267
|
+
}>, Schema.TaggedStruct<"Summarize", {
|
|
268
|
+
readonly keepFromTurn: Schema.Int;
|
|
269
|
+
readonly summary: Schema.String;
|
|
270
|
+
}>]>;
|
|
271
|
+
}>, Schema.TaggedStruct<"TurnEnded", {
|
|
272
|
+
readonly outcome: Schema.Literals<readonly ["completed", "partial", "failed"]>;
|
|
273
|
+
readonly reply: Schema.OptionFromNullOr<Schema.String>;
|
|
274
|
+
}>]>;
|
|
275
|
+
}>;
|
|
276
|
+
export type LogEntry = typeof LogEntry.Type;
|
|
277
|
+
/** The model-visible rebuild of the log for one request. */
|
|
278
|
+
export declare const BuiltContext: Schema.Struct<{
|
|
279
|
+
readonly messages: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
280
|
+
readonly role: Schema.Literal<"user">;
|
|
281
|
+
readonly content: Schema.String;
|
|
282
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
283
|
+
}>, Schema.Struct<{
|
|
284
|
+
readonly role: Schema.Literal<"assistant">;
|
|
285
|
+
readonly content: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
286
|
+
readonly type: Schema.Literal<"text">;
|
|
287
|
+
readonly text: Schema.String;
|
|
288
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
289
|
+
}>, Schema.Struct<{
|
|
290
|
+
readonly type: Schema.Literal<"reasoning">;
|
|
291
|
+
readonly text: Schema.String;
|
|
292
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
293
|
+
}>, Schema.Struct<{
|
|
294
|
+
readonly type: Schema.Literal<"tool-call">;
|
|
295
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
296
|
+
readonly toolName: Schema.String;
|
|
297
|
+
readonly input: Schema.Unknown;
|
|
298
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
299
|
+
readonly providerExecuted: Schema.optional<Schema.Boolean>;
|
|
300
|
+
}>]>>;
|
|
301
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
302
|
+
}>, Schema.Struct<{
|
|
303
|
+
readonly role: Schema.Literal<"tool">;
|
|
304
|
+
readonly content: Schema.$Array<Schema.Struct<{
|
|
305
|
+
readonly type: Schema.Literal<"tool-result">;
|
|
306
|
+
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
307
|
+
readonly toolName: Schema.String;
|
|
308
|
+
readonly output: Schema.Unknown;
|
|
309
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
310
|
+
readonly isError: Schema.optional<Schema.Boolean>;
|
|
311
|
+
}>>;
|
|
312
|
+
readonly providerOptions: Schema.optional<Schema.Unknown>;
|
|
313
|
+
}>]>>;
|
|
314
|
+
readonly fingerprint: Schema.String;
|
|
315
|
+
readonly estimatedTokens: Schema.Int;
|
|
316
|
+
readonly compactions: Schema.$Array<Schema.brand<Schema.Trimmed, "EntryId">>;
|
|
317
|
+
/** The last entry folded: what a rebuild must stop at. */
|
|
318
|
+
readonly through: Schema.OptionFromNullOr<Schema.brand<Schema.Trimmed, "EntryId">>;
|
|
319
|
+
}>;
|
|
320
|
+
export type BuiltContext = typeof BuiltContext.Type;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Option } from "effect";
|
|
2
|
+
import type { AgentMessage } from "../domain/message.entity.js";
|
|
3
|
+
import type { ArtifactRef, BuiltContext, CompactionAction, EntryId, LogBody, LogEntry, Subject } from "./memory-log.entity.js";
|
|
4
|
+
/** Key-sorted JSON: identical values always serialize to identical bytes. */
|
|
5
|
+
export declare const canonicalJson: (value: unknown) => string;
|
|
6
|
+
/** FNV-1a (32 bit) plus length — pure, stable across processes and runtimes. */
|
|
7
|
+
export declare const fingerprintOf: (text: string) => string;
|
|
8
|
+
/** A deliberately conservative estimate for mixed prose and JSON. */
|
|
9
|
+
export declare const estimateTokens: (text: string) => number;
|
|
10
|
+
export declare const estimateMessageTokens: (messages: ReadonlyArray<AgentMessage>) => number;
|
|
11
|
+
export declare const entryId: (runId: string, index: number) => EntryId;
|
|
12
|
+
export interface RenderOptions {
|
|
13
|
+
/** Only this strategy's compaction entries apply. */
|
|
14
|
+
readonly strategy: string;
|
|
15
|
+
readonly currentTurn: number;
|
|
16
|
+
readonly currentRun: string;
|
|
17
|
+
/** Turn contexts are shown for the current turn only, or for every kept turn. */
|
|
18
|
+
readonly turnContext: "current" | "all";
|
|
19
|
+
/** Append the host's delivered reply when the model's own text differs. */
|
|
20
|
+
readonly replies: boolean;
|
|
21
|
+
/** The current step's context closes the messages ("tail") or rides in the system prompt ("none"). */
|
|
22
|
+
readonly stepContext: "tail" | "none";
|
|
23
|
+
/** Show recorded tool digests in place of the results they digest. */
|
|
24
|
+
readonly digests: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* How artifacts reach the model. `none`: a reference line after the
|
|
27
|
+
* result's text. `inline` is reserved for image parts; messages carry text
|
|
28
|
+
* only today, so it renders like `none`.
|
|
29
|
+
*/
|
|
30
|
+
readonly media: {
|
|
31
|
+
readonly mode: "none" | "inline";
|
|
32
|
+
readonly maxImages: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** The reference lines a result's artifacts add to its text. */
|
|
36
|
+
export declare const artifactLines: (artifacts: ReadonlyArray<ArtifactRef>, maxImages: number) => string;
|
|
37
|
+
export declare const renderLog: (entries: ReadonlyArray<LogEntry>, options: RenderOptions) => ReadonlyArray<AgentMessage>;
|
|
38
|
+
export declare const buildContext: (entries: ReadonlyArray<LogEntry>, options: RenderOptions) => BuiltContext;
|
|
39
|
+
/** The latest step context recorded by this run, if any. */
|
|
40
|
+
export declare const stepContextOf: (entries: ReadonlyArray<LogEntry>, runId: string) => Option.Option<string>;
|
|
41
|
+
/** The number of the latest started turn (turns count from 1). */
|
|
42
|
+
export declare const currentTurnOf: (entries: ReadonlyArray<LogEntry>) => number;
|
|
43
|
+
/** Latest-wins subjects of the given kinds (every kind when empty). */
|
|
44
|
+
export declare const subjectsOf: (entries: ReadonlyArray<LogEntry>, kinds: ReadonlyArray<string>) => ReadonlyArray<Subject>;
|
|
45
|
+
/** Skills and tools activated so far, in activation order (only ever grows). */
|
|
46
|
+
export declare const activationsOf: (entries: ReadonlyArray<LogEntry>) => {
|
|
47
|
+
readonly skills: ReadonlyArray<string>;
|
|
48
|
+
readonly tools: ReadonlyArray<string>;
|
|
49
|
+
};
|
|
50
|
+
/** A compact user/assistant transcript for classifiers: user messages and replies only. */
|
|
51
|
+
export declare const referenceTranscript: (entries: ReadonlyArray<LogEntry>) => ReadonlyArray<AgentMessage>;
|
|
52
|
+
/** The full-fidelity transcript: messages with every tool result's encoded value. */
|
|
53
|
+
export declare const rawTranscript: (entries: ReadonlyArray<LogEntry>) => ReadonlyArray<AgentMessage>;
|
|
54
|
+
export interface LogQuery {
|
|
55
|
+
readonly kinds: ReadonlyArray<LogBody["_tag"]>;
|
|
56
|
+
readonly turns: Option.Option<{
|
|
57
|
+
readonly from: number;
|
|
58
|
+
readonly to: number;
|
|
59
|
+
}>;
|
|
60
|
+
readonly tool: Option.Option<string>;
|
|
61
|
+
readonly subject: Option.Option<{
|
|
62
|
+
readonly kind: string;
|
|
63
|
+
readonly id: Option.Option<string>;
|
|
64
|
+
}>;
|
|
65
|
+
readonly text: Option.Option<string>;
|
|
66
|
+
readonly limit: Option.Option<number>;
|
|
67
|
+
}
|
|
68
|
+
export declare const emptyQuery: LogQuery;
|
|
69
|
+
export declare const queryLog: (entries: ReadonlyArray<LogEntry>, query: LogQuery) => ReadonlyArray<LogEntry>;
|
|
70
|
+
/** Tool-call inputs by call id, from the log and an unrecorded tail. */
|
|
71
|
+
export declare const toolInputsOf: (entries: ReadonlyArray<LogEntry>, tail: ReadonlyArray<AgentMessage>) => ReadonlyMap<string, unknown>;
|
|
72
|
+
/** Entry ids this strategy already rewrote (compact views or spills). */
|
|
73
|
+
export declare const rewrittenBy: (entries: ReadonlyArray<LogEntry>, strategy: string) => ReadonlySet<string>;
|
|
74
|
+
/** A hypothetical compaction entry, for estimating a render before recording it. */
|
|
75
|
+
export declare const pendingCompaction: (strategy: {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly version: string;
|
|
78
|
+
}, action: CompactionAction, index: number) => LogEntry;
|
|
79
|
+
/**
|
|
80
|
+
* A compact, human-readable transcript of the given turns for digests and
|
|
81
|
+
* ledgers: user messages, assistant text, each tool result clipped, and replies.
|
|
82
|
+
* The head (the first request) always survives the cap.
|
|
83
|
+
*/
|
|
84
|
+
export declare const digestTranscript: (entries: ReadonlyArray<LogEntry>, caps: {
|
|
85
|
+
readonly result: number;
|
|
86
|
+
readonly total: number;
|
|
87
|
+
}) => string;
|
|
88
|
+
/** A generic ledger of condensed turns: what was asked, what was replied, what was found. */
|
|
89
|
+
export declare const ledgerOf: (entries: ReadonlyArray<LogEntry>, throughTurn: number, perTurnChars: number) => string;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Effect, Option, Schema } from "effect";
|
|
2
|
+
import { handoffToMessage } from "../loop/mapping.js";
|
|
3
|
+
import { EntryId as EntryIdSchema } from "./memory-log.entity.js";
|
|
4
|
+
export const canonicalJson = (value) => JSON.stringify(sortKeys(value)) ?? "null";
|
|
5
|
+
const sortKeys = (value) => Array.isArray(value) ? value.map(sortKeys) : typeof value === "object" && value !== null ? Object.fromEntries(Object.keys(value).sort().flatMap((key) => {
|
|
6
|
+
const field = value[key];
|
|
7
|
+
return field === void 0 ? [] : [[key, sortKeys(field)]];
|
|
8
|
+
})) : value;
|
|
9
|
+
export const fingerprintOf = (text) => {
|
|
10
|
+
return `${Array.from(text).reduce((h, ch) => Math.imul((h ^ ch.charCodeAt(0)) >>> 0, 16777619) >>> 0, 2166136261).toString(16).padStart(8, "0")}${text.length.toString(16)}`;
|
|
11
|
+
}, estimateTokens = (text) => Math.ceil(text.length / 3), estimateMessageTokens = (messages) => messages.reduce((sum, message) => sum + estimateTokens(canonicalJson(message)), 0), entryId = (runId, index) => EntryIdSchema.make(`${runId}:${index}`);
|
|
12
|
+
const digestsOf = (entries) => new Map(entries.flatMap((entry) => entry.body._tag === "ToolDigest" ? [[String(entry.body.entry), entry.body.text]] : [])), appliedCompactions = (entries, strategy) => entries.reduce((applied, entry) => {
|
|
13
|
+
if (entry.body._tag !== "Compaction" || entry.body.strategy !== strategy)
|
|
14
|
+
return applied;
|
|
15
|
+
const action = entry.body.action, ids = [...applied.ids, entry.id];
|
|
16
|
+
if (action._tag === "CompactViews")
|
|
17
|
+
return { ...applied, ids, views: new Map([...applied.views, ...action.entries.map((id, index) => [id, action.texts[index] ?? ""])]) };
|
|
18
|
+
if (action._tag === "Spill")
|
|
19
|
+
return { ...applied, ids, views: new Map([...applied.views, [action.entry, action.preview]]) };
|
|
20
|
+
if (action._tag === "DropTurns")
|
|
21
|
+
return action.throughTurn < applied.cutTurn ? { ...applied, ids } : {
|
|
22
|
+
...applied,
|
|
23
|
+
ids,
|
|
24
|
+
cutTurn: action.throughTurn,
|
|
25
|
+
preamble: Option.some({ role: "user", content: `[Host note: earlier turns were condensed to free context.]
|
|
26
|
+
|
|
27
|
+
${action.ledger}` })
|
|
28
|
+
};
|
|
29
|
+
return action.keepFromTurn - 1 < applied.cutTurn ? { ...applied, ids } : {
|
|
30
|
+
...applied,
|
|
31
|
+
ids,
|
|
32
|
+
cutTurn: action.keepFromTurn - 1,
|
|
33
|
+
preamble: Option.some(handoffToMessage(action.summary))
|
|
34
|
+
};
|
|
35
|
+
}, { views: new Map, cutTurn: 0, preamble: Option.none(), ids: [] }), toolCallIds = (message) => message.role === "assistant" ? message.content.flatMap((part) => part.type === "tool-call" ? [part.toolCallId] : []) : [], assistantText = (message) => {
|
|
36
|
+
if (message.role !== "assistant")
|
|
37
|
+
return Option.none();
|
|
38
|
+
const text = message.content.flatMap((part) => part.type === "text" ? [part.text] : []).join("");
|
|
39
|
+
return text.length === 0 ? Option.none() : Option.some(text);
|
|
40
|
+
}, mergeToolMessages = (messages) => messages.reduce((merged, message) => {
|
|
41
|
+
const last = merged[merged.length - 1];
|
|
42
|
+
return last !== void 0 && last.role === "tool" && message.role === "tool" ? [...merged.slice(0, -1), { role: "tool", content: [...last.content, ...message.content] }] : [...merged, message];
|
|
43
|
+
}, []);
|
|
44
|
+
export const artifactLines = (artifacts, maxImages) => {
|
|
45
|
+
if (artifacts.length === 0)
|
|
46
|
+
return "";
|
|
47
|
+
const shown = artifacts.slice(0, Math.max(0, maxImages)), more = artifacts.length - shown.length;
|
|
48
|
+
return [
|
|
49
|
+
"",
|
|
50
|
+
"Artifacts:",
|
|
51
|
+
...shown.map((artifact) => `- ${artifact.kind} ${artifact.id} (${artifact.mediaType})${Option.match(artifact.alt, { onNone: () => "", onSome: (alt) => `: ${alt}` })}`),
|
|
52
|
+
...more > 0 ? [`- \u2026 ${more} more`] : []
|
|
53
|
+
].join(`
|
|
54
|
+
`);
|
|
55
|
+
};
|
|
56
|
+
const userRoleMessage = (content) => ({ role: "user", content }), assistantMessage = (text) => ({ role: "assistant", content: [{ type: "text", text }] });
|
|
57
|
+
export const renderLog = (entries, options) => {
|
|
58
|
+
const applied = appliedCompactions(entries, options.strategy), digests = options.digests ? digestsOf(entries) : new Map, pinned = new Set(entries.flatMap((entry) => entry.body._tag === "ToolResult" && entry.body.pinned ? [String(entry.body.toolCallId)] : [])), kept = (entry) => entry.turn > applied.cutTurn, stepContext = options.stepContext === "none" ? [] : entries.filter((entry) => entry.body._tag === "StepContext" && entry.runId === options.currentRun).slice(-1), initial = { out: [], lastText: new Map }, body = entries.reduce((state, entry) => {
|
|
59
|
+
const b = entry.body;
|
|
60
|
+
if (b._tag === "TurnStarted")
|
|
61
|
+
return kept(entry) ? { ...state, out: [...state.out, userRoleMessage(b.userMessage.text)] } : state;
|
|
62
|
+
if (b._tag === "TurnContext")
|
|
63
|
+
return kept(entry) && (options.turnContext === "all" || entry.turn === options.currentTurn) ? { ...state, out: [...state.out, userRoleMessage(b.text)] } : state;
|
|
64
|
+
if (b._tag === "Message") {
|
|
65
|
+
const calls = toolCallIds(b.message), pinnedExchange = calls.length > 0 && calls.every((id) => pinned.has(id));
|
|
66
|
+
if (!kept(entry) && !pinnedExchange)
|
|
67
|
+
return state;
|
|
68
|
+
const text = assistantText(b.message);
|
|
69
|
+
return {
|
|
70
|
+
out: [...state.out, b.message],
|
|
71
|
+
lastText: Option.match(text, { onNone: () => state.lastText, onSome: (value) => new Map([...state.lastText, [entry.turn, value]]) })
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (b._tag === "ToolResult") {
|
|
75
|
+
if (!kept(entry) && !b.pinned)
|
|
76
|
+
return state;
|
|
77
|
+
const text = applied.views.get(entry.id) ?? digests.get(entry.id) ?? b.view, message = { role: "tool", content: [{
|
|
78
|
+
type: "tool-result",
|
|
79
|
+
toolCallId: b.toolCallId,
|
|
80
|
+
toolName: b.toolName,
|
|
81
|
+
output: `${text}${artifactLines(b.artifacts, options.media.maxImages)}`,
|
|
82
|
+
isError: b.isError
|
|
83
|
+
}] };
|
|
84
|
+
return { ...state, out: [...state.out, message] };
|
|
85
|
+
}
|
|
86
|
+
if (b._tag === "TurnEnded" && options.replies && kept(entry))
|
|
87
|
+
return Option.match(b.reply, {
|
|
88
|
+
onNone: () => state,
|
|
89
|
+
onSome: (reply) => reply.length === 0 || state.lastText.get(entry.turn) === reply ? state : { ...state, out: [...state.out, assistantMessage(reply)] }
|
|
90
|
+
});
|
|
91
|
+
return state;
|
|
92
|
+
}, initial).out, tail = stepContext.flatMap((entry) => entry.body._tag === "StepContext" ? [userRoleMessage(entry.body.text)] : []);
|
|
93
|
+
return mergeToolMessages([...Option.toArray(applied.preamble), ...body, ...tail]);
|
|
94
|
+
}, buildContext = (entries, options) => {
|
|
95
|
+
const messages = renderLog(entries, options);
|
|
96
|
+
return {
|
|
97
|
+
messages,
|
|
98
|
+
fingerprint: fingerprintOf(canonicalJson(messages)),
|
|
99
|
+
estimatedTokens: estimateMessageTokens(messages),
|
|
100
|
+
compactions: appliedCompactions(entries, options.strategy).ids,
|
|
101
|
+
through: Option.fromNullishOr(entries.at(-1)?.id)
|
|
102
|
+
};
|
|
103
|
+
}, stepContextOf = (entries, runId) => Option.fromNullishOr(entries.flatMap((entry) => entry.body._tag === "StepContext" && entry.runId === runId ? [entry.body.text] : []).at(-1)), currentTurnOf = (entries) => entries.reduce((turn, entry) => entry.body._tag === "TurnStarted" ? Math.max(turn, entry.turn) : turn, 0), subjectsOf = (entries, kinds) => {
|
|
104
|
+
const all = entries.flatMap((entry) => entry.body._tag === "ToolResult" ? entry.body.subjects : []).filter((subject) => kinds.length === 0 || kinds.includes(subject.kind));
|
|
105
|
+
return [...new Map(all.map((subject) => [`${subject.kind}\x00${subject.id}`, subject])).values()];
|
|
106
|
+
}, activationsOf = (entries) => entries.reduce((active, entry) => entry.body._tag !== "ToolsActivated" ? active : {
|
|
107
|
+
skills: [...active.skills, ...entry.body.skills.filter((skill) => !active.skills.includes(skill))],
|
|
108
|
+
tools: [...active.tools, ...entry.body.tools.filter((tool) => !active.tools.includes(tool))]
|
|
109
|
+
}, { skills: [], tools: [] }), referenceTranscript = (entries) => entries.flatMap((entry) => {
|
|
110
|
+
if (entry.body._tag === "TurnStarted")
|
|
111
|
+
return [userRoleMessage(entry.body.userMessage.text)];
|
|
112
|
+
if (entry.body._tag === "TurnEnded")
|
|
113
|
+
return Option.match(entry.body.reply, { onNone: () => [], onSome: (text) => text.length === 0 ? [] : [{ role: "assistant", content: [{ type: "text", text }] }] });
|
|
114
|
+
return [];
|
|
115
|
+
}), rawTranscript = (entries) => mergeToolMessages(entries.flatMap((entry) => {
|
|
116
|
+
if (entry.body._tag === "TurnStarted")
|
|
117
|
+
return [userRoleMessage(entry.body.userMessage.text)];
|
|
118
|
+
if (entry.body._tag === "Message")
|
|
119
|
+
return [entry.body.message];
|
|
120
|
+
if (entry.body._tag === "ToolResult")
|
|
121
|
+
return [{ role: "tool", content: [{ type: "tool-result", toolCallId: entry.body.toolCallId, toolName: entry.body.toolName, output: entry.body.encoded, isError: entry.body.isError }] }];
|
|
122
|
+
return [];
|
|
123
|
+
})), emptyQuery = {
|
|
124
|
+
kinds: [],
|
|
125
|
+
turns: Option.none(),
|
|
126
|
+
tool: Option.none(),
|
|
127
|
+
subject: Option.none(),
|
|
128
|
+
text: Option.none(),
|
|
129
|
+
limit: Option.none()
|
|
130
|
+
}, queryLog = (entries, query) => {
|
|
131
|
+
const matches = entries.filter((entry) => (query.kinds.length === 0 || query.kinds.includes(entry.body._tag)) && Option.match(query.turns, { onNone: () => !0, onSome: (range) => entry.turn >= range.from && entry.turn <= range.to }) && Option.match(query.tool, { onNone: () => !0, onSome: (tool) => entry.body._tag === "ToolResult" && entry.body.toolName === tool }) && Option.match(query.subject, {
|
|
132
|
+
onNone: () => !0,
|
|
133
|
+
onSome: (wanted) => entry.body._tag === "ToolResult" && entry.body.subjects.some((subject) => subject.kind === wanted.kind && Option.match(wanted.id, { onNone: () => !0, onSome: (id) => subject.id === id }))
|
|
134
|
+
}) && Option.match(query.text, { onNone: () => !0, onSome: (text) => canonicalJson(entry.body).toLowerCase().includes(text.toLowerCase()) }));
|
|
135
|
+
return Option.match(query.limit, { onNone: () => matches, onSome: (limit) => matches.slice(-limit) });
|
|
136
|
+
}, toolInputsOf = (entries, tail) => new Map([
|
|
137
|
+
...entries.flatMap((entry) => entry.body._tag === "Message" ? [entry.body.message] : []),
|
|
138
|
+
...tail
|
|
139
|
+
].flatMap((message) => message.role === "assistant" ? message.content.flatMap((part) => part.type === "tool-call" ? [[String(part.toolCallId), part.input]] : []) : [])), rewrittenBy = (entries, strategy) => new Set(entries.flatMap((entry) => entry.body._tag !== "Compaction" || entry.body.strategy !== strategy ? [] : entry.body.action._tag === "CompactViews" ? entry.body.action.entries.map(String) : entry.body.action._tag === "Spill" ? [String(entry.body.action.entry)] : [])), pendingCompaction = (strategy, action, index) => ({
|
|
140
|
+
id: EntryIdSchema.make(`pending:${index}`),
|
|
141
|
+
runId: "",
|
|
142
|
+
turn: 0,
|
|
143
|
+
step: 0,
|
|
144
|
+
at: 0,
|
|
145
|
+
body: { _tag: "Compaction", strategy: strategy.id, version: strategy.version, action }
|
|
146
|
+
});
|
|
147
|
+
const clipText = (text, max) => text.length <= max ? text : `${text.slice(0, max)}\u2026`;
|
|
148
|
+
export const digestTranscript = (entries, caps) => {
|
|
149
|
+
const lines = entries.flatMap((entry) => {
|
|
150
|
+
const body = entry.body;
|
|
151
|
+
if (body._tag === "TurnStarted")
|
|
152
|
+
return [`User: ${body.userMessage.text}`];
|
|
153
|
+
if (body._tag === "Message")
|
|
154
|
+
return Option.toArray(Option.map(assistantText(body.message), (text) => `Assistant: ${text}`));
|
|
155
|
+
if (body._tag === "ToolResult")
|
|
156
|
+
return [`Tool ${body.toolName}${body.isError ? " (failed)" : ""}: ${clipText(body.view, caps.result)}`];
|
|
157
|
+
if (body._tag === "TurnEnded")
|
|
158
|
+
return Option.toArray(Option.map(body.reply, (reply) => `Reply: ${reply}`));
|
|
159
|
+
return [];
|
|
160
|
+
}), joined = lines.join(`
|
|
161
|
+
`);
|
|
162
|
+
if (joined.length <= caps.total)
|
|
163
|
+
return joined;
|
|
164
|
+
const head = lines[0] ?? "";
|
|
165
|
+
return `${head}
|
|
166
|
+
[\u2026earlier transcript clipped\u2026]
|
|
167
|
+
${lines.slice(1).join(`
|
|
168
|
+
`).slice(-(caps.total - head.length))}`;
|
|
169
|
+
}, ledgerOf = (entries, throughTurn, perTurnChars) => {
|
|
170
|
+
const kept = entries.filter((entry) => entry.turn <= throughTurn), lines = [...new Set(kept.map((entry) => entry.turn))].sort((left, right) => left - right).flatMap((turn) => {
|
|
171
|
+
const inTurn = kept.filter((entry) => entry.turn === turn), asked = inTurn.flatMap((entry) => entry.body._tag === "TurnStarted" ? [entry.body.userMessage.text] : []).at(0), reply = inTurn.flatMap((entry) => entry.body._tag === "TurnEnded" ? Option.toArray(entry.body.reply) : []).at(-1);
|
|
172
|
+
return asked === void 0 ? [] : [`- Asked: ${clipText(asked, perTurnChars)}${reply === void 0 ? "" : ` \u2014 replied: ${clipText(reply, perTurnChars)}`}`];
|
|
173
|
+
}), subjects = subjectsOf(kept, []).slice(-24).map((subject) => `${subject.kind} ${subject.id}${Option.match(subject.label, { onNone: () => "", onSome: (label) => ` (${label})` })}`);
|
|
174
|
+
return [
|
|
175
|
+
"Earlier in this conversation:",
|
|
176
|
+
...lines,
|
|
177
|
+
...subjects.length === 0 ? [] : [`Known subjects: ${subjects.join("; ")}`]
|
|
178
|
+
].join(`
|
|
179
|
+
`);
|
|
180
|
+
};
|