@xandreed/core 0.12.0 → 0.14.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.functions.d.ts +34 -0
- package/dist/conformance.functions.js +25 -0
- package/dist/domain/bash.entity.d.ts +82 -0
- package/dist/domain/bash.entity.functions.d.ts +17 -0
- package/dist/domain/bash.entity.functions.js +26 -0
- package/dist/domain/bash.entity.functions.test.d.ts +1 -0
- package/dist/domain/bash.entity.js +30 -0
- package/dist/domain/file-system.entity.d.ts +91 -0
- package/dist/domain/file-system.entity.functions.d.ts +18 -0
- package/dist/domain/file-system.entity.functions.js +30 -0
- package/dist/domain/file-system.entity.js +37 -0
- package/dist/domain/loop-event.entity.d.ts +9 -0
- package/dist/domain/media.entity.d.ts +79 -0
- package/dist/domain/media.entity.functions.d.ts +76 -0
- package/dist/domain/media.entity.functions.js +110 -0
- package/dist/domain/media.entity.js +38 -0
- package/dist/domain/media.entity.test.d.ts +1 -0
- package/dist/domain/message.entity.d.ts +157 -5
- package/dist/domain/message.entity.js +8 -4
- package/dist/domain/model-catalog.entity.d.ts +2 -0
- package/dist/domain/model-catalog.entity.js +2 -1
- package/dist/domain/token-usage.entity.d.ts +45 -1
- package/dist/domain/token-usage.entity.functions.d.ts +30 -1
- package/dist/domain/token-usage.entity.functions.js +24 -4
- package/dist/domain/token-usage.entity.js +19 -2
- package/dist/domain/token-usage.entity.test.d.ts +1 -0
- package/dist/harness/capability.entity.functions.js +2 -1
- package/dist/harness/plugin.adapter.d.ts +3 -0
- package/dist/harness/plugin.adapter.js +7 -1
- package/dist/harness/skill-files.entity.d.ts +101 -0
- package/dist/harness/skill-files.entity.functions.d.ts +58 -0
- package/dist/harness/skill-files.entity.functions.js +150 -0
- package/dist/harness/skill-files.entity.functions.test.d.ts +1 -0
- package/dist/harness/skill-files.entity.js +25 -0
- package/dist/index.d.ts +48 -10
- package/dist/index.js +67 -14
- package/dist/loop/cacheKey.d.ts +1 -1
- package/dist/loop/mapping.d.ts +13 -7
- package/dist/loop/mapping.js +30 -6
- package/dist/loop/stepContext.d.ts +8 -0
- package/dist/loop/stepContext.js +1 -1
- package/dist/loop/traceContent.d.ts +29 -0
- package/dist/loop/traceContent.js +4 -0
- package/dist/loop/traceContent.test.d.ts +1 -0
- package/dist/memory/memory-log.entity.d.ts +152 -14
- package/dist/memory/memory-log.entity.functions.d.ts +35 -5
- package/dist/memory/memory-log.entity.functions.js +59 -20
- package/dist/memory/memory-log.entity.js +19 -6
- package/dist/memory/memory-session.js +10 -6
- package/dist/memory/memory.conformance.d.ts +8 -1
- package/dist/memory/memory.conformance.js +32 -0
- package/dist/models/auth-store.conformance.d.ts +13 -0
- package/dist/models/auth-store.conformance.js +73 -0
- package/dist/models/auth-store.conformance.test.d.ts +1 -0
- package/dist/models/model-catalog.conformance.d.ts +15 -0
- package/dist/models/model-catalog.conformance.js +27 -0
- package/dist/models/model-catalog.conformance.test.d.ts +1 -0
- package/dist/models/settings-store.conformance.d.ts +26 -0
- package/dist/models/settings-store.conformance.js +80 -0
- package/dist/models/settings-store.conformance.test.d.ts +1 -0
- package/dist/ports/activity.port.d.ts +35 -0
- package/dist/ports/activity.port.js +7 -0
- package/dist/ports/auth-store.port.d.ts +7 -5
- package/dist/ports/bash.port.d.ts +22 -0
- package/dist/ports/bash.port.js +4 -0
- package/dist/ports/capability.port.d.ts +11 -1
- package/dist/ports/file-system.port.d.ts +44 -15
- package/dist/ports/file-system.port.js +1 -7
- package/dist/ports/harness.port.d.ts +1 -99
- package/dist/ports/harness.port.js +0 -24
- package/dist/ports/memory.port.d.ts +13 -1
- package/dist/ports/model-catalog.port.d.ts +4 -1
- package/dist/ports/run-context.port.d.ts +6 -1
- package/dist/ports/session-log.port.d.ts +4 -1
- package/dist/ports/sessions.port.d.ts +35 -6
- package/dist/ports/settings-store.port.d.ts +11 -7
- package/dist/ports/step-loop.port.d.ts +30 -2
- package/dist/ports/tasks.port.d.ts +5 -1
- package/dist/ports/turn-admission.port.d.ts +8 -2
- package/dist/ports/turn-events.port.d.ts +8 -1
- package/dist/ports/turn-scope.port.d.ts +19 -1
- package/dist/ports/turn.port.d.ts +70 -5
- package/dist/ports/utility-llm.port.d.ts +2 -0
- package/dist/session/activity.entity.d.ts +42 -0
- package/dist/session/activity.entity.functions.d.ts +21 -0
- package/dist/session/activity.entity.functions.js +23 -0
- package/dist/session/activity.entity.functions.test.d.ts +1 -0
- package/dist/session/activity.entity.js +15 -0
- package/dist/session/chassis.js +2 -1
- package/dist/session/session-event.entity.d.ts +68 -6
- package/dist/session/session-event.entity.functions.d.ts +34 -2
- package/dist/session/session-event.entity.functions.js +54 -16
- package/dist/session/session-event.entity.js +19 -2
- package/dist/session/session-log.conformance.d.ts +4 -3
- package/dist/session/session-log.conformance.js +16 -14
- package/dist/session/session-log.recovery.conformance.d.ts +40 -0
- package/dist/session/session-log.recovery.conformance.js +143 -0
- package/dist/session/session-log.recovery.conformance.test.d.ts +1 -0
- package/dist/session/sessions.conformance.d.ts +79 -0
- package/dist/session/sessions.conformance.js +355 -0
- package/dist/session/sessions.entity.d.ts +36 -3
- package/dist/session/sessions.entity.functions.d.ts +42 -0
- package/dist/session/sessions.entity.functions.js +26 -0
- package/dist/session/sessions.entity.js +17 -4
- package/dist/session/sessions.entity.test.d.ts +1 -0
- package/dist/session/tasks.conformance.d.ts +70 -0
- package/dist/session/tasks.conformance.js +213 -0
- package/dist/session/tasks.conformance.test.d.ts +1 -0
- package/dist/turn/model-request.entity.d.ts +4 -0
- package/dist/turn/model-request.entity.js +7 -2
- package/dist/turn/step-loop.conformance.d.ts +4 -1
- package/dist/turn/step-loop.conformance.js +73 -7
- package/dist/turn/tool-registry.conformance.d.ts +38 -0
- package/dist/turn/tool-registry.conformance.js +206 -0
- package/dist/turn/turn-bus.js +4 -3
- package/dist/turn/turn-event.entity.d.ts +134 -34
- package/dist/turn/turn-event.entity.functions.d.ts +11 -0
- package/dist/turn/turn-event.entity.functions.js +9 -1
- package/dist/turn/turn-event.entity.js +76 -3
- package/dist/turn/turn-event.entity.test.d.ts +1 -0
- package/dist/turn/turn-lifecycle.d.ts +46 -8
- package/dist/turn/turn-lifecycle.js +33 -3
- package/dist/turn/turn-lifecycle.test.d.ts +1 -0
- package/dist/turn/turn-run.d.ts +15 -3
- package/dist/turn/turn-run.js +37 -9
- package/dist/turn/turn.adapter.d.ts +6 -5
- package/dist/turn/turn.adapter.js +15 -3
- package/dist/turn/turn.conformance.d.ts +11 -2
- package/dist/turn/turn.conformance.js +63 -2
- package/dist/turn/user-message.entity.d.ts +14 -4
- package/dist/turn/user-message.entity.functions.d.ts +21 -0
- package/dist/turn/user-message.entity.functions.js +24 -0
- package/dist/turn/user-message.entity.js +6 -8
- package/dist/turn/user-message.entity.test.d.ts +1 -0
- package/dist/util/glob.d.ts +30 -0
- package/dist/util/glob.js +91 -0
- package/dist/util/glob.test.d.ts +1 -0
- package/dist/workspace/bash.conformance.d.ts +37 -0
- package/dist/workspace/bash.conformance.js +120 -0
- package/dist/workspace/bash.conformance.test.d.ts +1 -0
- package/dist/workspace/file-system.conformance.d.ts +38 -0
- package/dist/workspace/file-system.conformance.js +254 -0
- package/dist/workspace/file-system.conformance.test.d.ts +1 -0
- package/dist/workspace/file-system.memory.adapter.d.ts +23 -0
- package/dist/workspace/file-system.memory.adapter.js +132 -0
- package/package.json +6 -6
- package/dist/harness/session.entity.d.ts +0 -33
- package/dist/harness/session.entity.js +0 -25
- package/dist/harness/skill-files.d.ts +0 -17
- package/dist/harness/skill-files.js +0 -25
- package/dist/ports/conversation-store.port.d.ts +0 -127
- package/dist/ports/conversation-store.port.js +0 -36
- package/dist/ports/shell.port.d.ts +0 -32
- package/dist/ports/shell.port.js +0 -16
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Tool } from "effect/ai";
|
|
2
|
+
import { Context, Effect, Layer, Option, Ref, Schema, Stream } from "effect";
|
|
3
|
+
import { check, expect, freshId } from "../conformance.functions.js";
|
|
4
|
+
import { Failure } from "../domain/failure.entity.js";
|
|
5
|
+
import { defineCapability, defineSkill, defineTool } from "../harness/capability.entity.functions.js";
|
|
6
|
+
import { HarnessError } from "../harness/plugin.entity.js";
|
|
7
|
+
import { CurrentAgentStep, CurrentToolInvocation } from "../loop/stepContext.js";
|
|
8
|
+
import { EntryId } from "../memory/memory-log.entity.js";
|
|
9
|
+
import { openLogSession } from "../memory/memory-session.js";
|
|
10
|
+
import { ActionPolicy } from "../ports/harness.port.js";
|
|
11
|
+
import { ConversationMemory } from "../ports/memory.port.js";
|
|
12
|
+
import { PermissionGrants } from "../ports/permission.port.js";
|
|
13
|
+
import { RunContext } from "../ports/run-context.port.js";
|
|
14
|
+
import { ToolRegistry } from "../ports/tool-registry.port.js";
|
|
15
|
+
import { TurnMemory, TurnToolbox } from "../ports/turn-scope.port.js";
|
|
16
|
+
import { turnStartedDraft } from "../session/session-event.entity.functions.js";
|
|
17
|
+
import { TurnLive } from "./turn.adapter.js";
|
|
18
|
+
import { publishToolProgress } from "./turn-event.entity.functions.js";
|
|
19
|
+
import { UserMessage } from "./user-message.entity.js";
|
|
20
|
+
const RUN = "conformance-run", Opener = Context.Reference("efferent/conformance/ToolRegistryOpener", { defaultValue: () => "none" }), Ping = Tool.make("kit_ping", {
|
|
21
|
+
description: "Answer pong, at most twice per turn.",
|
|
22
|
+
parameters: Schema.Struct({ n: Schema.Int }),
|
|
23
|
+
success: Schema.String,
|
|
24
|
+
failure: Failure,
|
|
25
|
+
failureMode: "return"
|
|
26
|
+
}), Report = Tool.make("kit_report", {
|
|
27
|
+
description: "Report progress twice, then say which call this was.",
|
|
28
|
+
parameters: Schema.Struct({}),
|
|
29
|
+
success: Schema.String,
|
|
30
|
+
failure: Failure,
|
|
31
|
+
failureMode: "return"
|
|
32
|
+
}), Vault = Tool.make("kit_vault", {
|
|
33
|
+
description: "Read the vault, with the kit.vault permission.",
|
|
34
|
+
parameters: Schema.Struct({ key: Schema.String }),
|
|
35
|
+
success: Schema.String,
|
|
36
|
+
failure: Failure,
|
|
37
|
+
failureMode: "return"
|
|
38
|
+
}), Archive = Tool.make("kit_archive", {
|
|
39
|
+
description: "Archive a record, once its skill is loaded.",
|
|
40
|
+
parameters: Schema.Struct({ id: Schema.String }),
|
|
41
|
+
success: Schema.String,
|
|
42
|
+
failure: Failure,
|
|
43
|
+
failureMode: "return"
|
|
44
|
+
}), TOOLS = [Ping.name, Report.name, Vault.name, Archive.name], SKILLS = ["kit.basics", "kit.vault", "kit.archiving"], capabilityOf = (ran) => {
|
|
45
|
+
const note = (tool) => Ref.update(ran, (all) => [...all, tool]);
|
|
46
|
+
return defineCapability({
|
|
47
|
+
id: "conformance.tools",
|
|
48
|
+
version: "1",
|
|
49
|
+
tools: [
|
|
50
|
+
defineTool({ tool: Ping, handler: ({ n }) => note(Ping.name).pipe(Effect.as(`pong ${n}`)), annotations: { maxCallsPerRun: Option.some(2) } }),
|
|
51
|
+
defineTool({
|
|
52
|
+
tool: Report,
|
|
53
|
+
handler: () => Effect.gen(function* () {
|
|
54
|
+
yield* note(Report.name);
|
|
55
|
+
const invocation = yield* Effect.service(CurrentToolInvocation), opener = yield* Effect.service(Opener);
|
|
56
|
+
yield* publishToolProgress("stdout", "working");
|
|
57
|
+
yield* publishToolProgress("status", "half way");
|
|
58
|
+
return `${Option.match(invocation, { onNone: () => "no invocation", onSome: (call) => `${call.invocationId}|${call.tool}|${call.step}` })}|${opener}`;
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
defineTool({ tool: Vault, handler: ({ key }) => note(Vault.name).pipe(Effect.as(`secret ${key}`)), annotations: { permissions: ["kit.vault"] } }),
|
|
62
|
+
defineTool({ tool: Archive, handler: ({ id }) => note(Archive.name).pipe(Effect.as(`archived ${id}`)) })
|
|
63
|
+
],
|
|
64
|
+
skills: [
|
|
65
|
+
defineSkill({ id: "kit.basics", summary: "Ping and report.", tools: [Ping.name, Report.name], always: !0 }),
|
|
66
|
+
defineSkill({ id: "kit.vault", summary: "Read the vault.", tools: [Vault.name], permissions: ["kit.vault"] }),
|
|
67
|
+
defineSkill({ id: "kit.archiving", summary: "Archive records.", tools: [Archive.name] })
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
}, policy = {
|
|
71
|
+
strategy: { id: "conformance", version: "1" },
|
|
72
|
+
render: { turnContext: "current", replies: !0, digests: !0, media: { mode: "none", maxImages: 0, maxChars: 0 } },
|
|
73
|
+
digestOnWrite: Option.none(),
|
|
74
|
+
maintain: () => Effect.succeed({ actions: [], digest: [] })
|
|
75
|
+
}, memoryWriter = Effect.gen(function* () {
|
|
76
|
+
const session = yield* freshId, userMessage = new UserMessage({ text: "use the tools" }), draft = yield* turnStartedDraft(1, {
|
|
77
|
+
runId: RUN,
|
|
78
|
+
key: RUN,
|
|
79
|
+
origin: "user",
|
|
80
|
+
userMessage,
|
|
81
|
+
command: {},
|
|
82
|
+
claimed: [],
|
|
83
|
+
entry: EntryId.make(`${RUN}:0`),
|
|
84
|
+
at: 0
|
|
85
|
+
}).pipe(Effect.orDie), started = { session, seq: 1, turn: draft.turn, kind: draft.kind, at: 0, data: draft.data }, stored = yield* Ref.make([started]), append = (drafts) => Ref.update(stored, (all) => [...all, ...drafts.map((next, index) => ({
|
|
86
|
+
session,
|
|
87
|
+
seq: all.length + index + 1,
|
|
88
|
+
turn: Option.some(1),
|
|
89
|
+
kind: next.kind,
|
|
90
|
+
at: 0,
|
|
91
|
+
data: JSON.parse(JSON.stringify(next.data))
|
|
92
|
+
}))]);
|
|
93
|
+
return { writer: {
|
|
94
|
+
admitted: { session: { id: session, owner: "conformance" }, turn: 1, runId: RUN, key: RUN, origin: "user", userMessage, command: {}, claimed: [], meta: {} },
|
|
95
|
+
started,
|
|
96
|
+
history: () => Effect.succeed([]),
|
|
97
|
+
snapshot: (kinds, after) => Ref.get(stored).pipe(Effect.map((events) => events.filter((event) => (after === void 0 || event.seq > after) && (kinds.length === 0 || kinds.includes(event.kind))))),
|
|
98
|
+
append,
|
|
99
|
+
write: (op) => op,
|
|
100
|
+
transact: (decide) => decide([]).pipe(Effect.flatMap((decision) => append(decision.drafts).pipe(Effect.as({ result: decision.result, events: [] })))),
|
|
101
|
+
flush: Effect.void,
|
|
102
|
+
end: () => Effect.succeed({ pending: 0 }),
|
|
103
|
+
closed: Effect.never
|
|
104
|
+
}, stored: Ref.get(stored) };
|
|
105
|
+
}), messageOf = (result) => typeof result === "object" && result !== null && ("message" in result) && typeof result.message === "string" ? result.message : "none";
|
|
106
|
+
export const toolRegistryConformance = (make) => {
|
|
107
|
+
const inTurn = (setup, body) => Effect.scoped(Effect.gen(function* () {
|
|
108
|
+
const ran = yield* Ref.make([]), registry = yield* make([capabilityOf(ran)], { maxCallsPerRun: setup.maxCallsPerRun ?? 32 }), recorded = yield* memoryWriter, services = Layer.merge(Layer.succeed(ConversationMemory, ConversationMemory.of({ strategy: policy.strategy, open: ({ runId, log }) => openLogSession(log, policy, { runId }) })), Layer.succeed(ToolRegistry, registry));
|
|
109
|
+
return yield* Effect.gen(function* () {
|
|
110
|
+
const run = yield* RunContext, published = yield* Ref.make([]);
|
|
111
|
+
yield* run.events.subscribe((event) => event._tag.startsWith("tool.") || event._tag === "skills.activated" ? Option.some(event) : Option.none(), (event) => Ref.update(published, (all) => [...all, event]));
|
|
112
|
+
const memory = yield* TurnMemory;
|
|
113
|
+
yield* memory.persistMessage;
|
|
114
|
+
if (setup.vaultActive === !0)
|
|
115
|
+
yield* memory.session.record([{ _tag: "ToolsActivated", skills: ["kit.vault"], tools: [Vault.name], source: "host", decision: Option.none() }], 0);
|
|
116
|
+
const tools = yield* (yield* TurnToolbox).open.pipe(Effect.provideService(Opener, "the opener's"), (open) => Option.match(Option.fromNullishOr(setup.policy), { onNone: () => open, onSome: (denying) => open.pipe(Effect.provideService(ActionPolicy, denying)) }), (open) => Option.match(Option.fromNullishOr(setup.grants), {
|
|
117
|
+
onNone: () => open,
|
|
118
|
+
onSome: (grants) => open.pipe(Effect.provideService(PermissionGrants, PermissionGrants.of({ grants: Effect.succeed(new Set(grants)) })))
|
|
119
|
+
}));
|
|
120
|
+
yield* tools.select(run.userMessage);
|
|
121
|
+
const call = (name, params, step = 1) => Effect.gen(function* () {
|
|
122
|
+
const kit = yield* tools.toolkit.pipe(Effect.provide(tools.handlers)), last = (yield* Stream.runCollect(yield* kit.handle(name, params))).at(-1);
|
|
123
|
+
return { isFailure: last?.isFailure ?? !0, result: last?.result };
|
|
124
|
+
}).pipe(Effect.provideService(CurrentAgentStep, Option.some(step)), Effect.provideService(Opener, "the caller's"));
|
|
125
|
+
return yield* body({
|
|
126
|
+
catalog: registry.catalog,
|
|
127
|
+
tools,
|
|
128
|
+
call,
|
|
129
|
+
published: run.flush.pipe(Effect.orDie, Effect.andThen(Ref.get(published))),
|
|
130
|
+
stored: run.flush.pipe(Effect.orDie, Effect.andThen(recorded.stored)),
|
|
131
|
+
ran: Ref.get(ran)
|
|
132
|
+
});
|
|
133
|
+
}).pipe(Effect.provide(TurnLive({ turn: recorded.writer })), Effect.provide(services));
|
|
134
|
+
})), tags = (events) => events.map((event) => event._tag).join(), idOf = (event) => ("invocationId" in event) ? event.invocationId : "", refusal = (reason, id, calls, setup = {}) => check(`a call refused as ${reason} takes its invocation id first, publishes one tool.refused and no tool.started on the run's bus only, and never runs the handler`, id, () => inTurn(setup, (turn) => Effect.gen(function* () {
|
|
135
|
+
const refusedAt = calls.length - 1, [tool, input] = calls[refusedAt], results = yield* Effect.forEach(calls, ([name, params]) => turn.call(name, params)), events = yield* turn.published, refused = events.flatMap((event) => event._tag === "tool.refused" ? [event] : []);
|
|
136
|
+
yield* expect(id, refused.length === 1, `the calls published ${refused.length} tool.refused`);
|
|
137
|
+
const event = refused[0];
|
|
138
|
+
yield* expect(id, event.reason === reason && event.tool === tool && event.step === 1 && JSON.stringify(event.input) === JSON.stringify(input), `the refusal was published as ${event.reason} of ${event.tool} at step ${event.step} with input ${JSON.stringify(event.input)}`);
|
|
139
|
+
yield* expect(id, event.invocationId === `${RUN}:call:${refusedAt}`, `the refused call's id is ${event.invocationId}, not ${RUN}:call:${refusedAt} (ids are minted before the checks)`);
|
|
140
|
+
const outcome = results[refusedAt];
|
|
141
|
+
yield* expect(id, outcome.isFailure && messageOf(outcome.result) === event.message && event.message.length > 0, `the refused call returned ${JSON.stringify(outcome.result)} (failure: ${outcome.isFailure}), not the refusal's message "${event.message}"`);
|
|
142
|
+
const others = events.filter((other) => other._tag !== "tool.refused" && idOf(other) === event.invocationId);
|
|
143
|
+
yield* expect(id, others.length === 0, `the refused call also published ${tags(others)}`);
|
|
144
|
+
const ran = yield* turn.ran;
|
|
145
|
+
yield* expect(id, ran.length === refusedAt, `the handlers ran for ${ran.join() || "no call"}; only the ${refusedAt} calls before the refused one should have`);
|
|
146
|
+
const stored = (yield* turn.stored).filter((record) => record.kind === "tool.refused");
|
|
147
|
+
yield* expect(id, stored.length === 1 && stored[0].data.invocationId === event.invocationId && !("input" in stored[0].data), `the turn stored ${stored.length} tool.refused${stored.length > 0 && "input" in stored[0].data ? ", with the call's input" : ""} (a registry publishes on the run's bus and never writes the journal itself)`);
|
|
148
|
+
})));
|
|
149
|
+
return [
|
|
150
|
+
check("the catalog and a run's tools hold every tool and skill the capabilities define, with their permissions", "catalog", () => inTurn({}, (turn) => Effect.gen(function* () {
|
|
151
|
+
const recipes = turn.catalog.recipes.map((recipe) => recipe.id), listed = turn.catalog.tools.map((tool) => tool.id);
|
|
152
|
+
yield* expect("catalog", SKILLS.every((skill) => recipes.includes(skill)) && TOOLS.every((tool) => listed.includes(tool)), `the catalog lists the skills ${recipes.join()} and the tools ${listed.join()}`);
|
|
153
|
+
const vault = turn.catalog.tools.find((tool) => tool.id === Vault.name);
|
|
154
|
+
yield* expect("catalog", vault?.permissions.join() === "kit.vault", `the catalog gives ${Vault.name} the permissions [${vault?.permissions.join() ?? "of no entry"}]`);
|
|
155
|
+
const skills = turn.tools.skills.map((skill) => skill.id);
|
|
156
|
+
yield* expect("catalog", SKILLS.every((skill) => skills.includes(skill)), `the run's skills are ${skills.join()}`);
|
|
157
|
+
const toolkit = Object.keys(turn.tools.toolkit.tools);
|
|
158
|
+
yield* expect("catalog", TOOLS.every((tool) => toolkit.includes(tool)), `the run's toolkit holds ${toolkit.join()}`);
|
|
159
|
+
}))),
|
|
160
|
+
check("always-on skills are active, others only once activated; an unknown skill is refused and an ungranted one stays inactive; the active set only grows", "activation", () => inTurn({}, (turn) => Effect.gen(function* () {
|
|
161
|
+
const active = yield* turn.tools.active;
|
|
162
|
+
yield* expect("activation", active.includes(Ping.name) && active.includes(Report.name) && !active.includes(Archive.name) && !active.includes(Vault.name), `with only the always-on skill applied, the active tools are ${active.join()}`);
|
|
163
|
+
const returned = yield* turn.tools.activate(["kit.archiving"], "host"), grown = yield* turn.tools.active;
|
|
164
|
+
yield* expect("activation", returned.includes(Archive.name) && grown.includes(Archive.name) && active.every((tool) => grown.includes(tool)), `after activating kit.archiving the active tools are ${grown.join()} (activate returned ${returned.join()})`);
|
|
165
|
+
yield* turn.tools.activate(["kit.archiving"], "host");
|
|
166
|
+
const again = yield* turn.tools.active;
|
|
167
|
+
yield* expect("activation", again.join() === grown.join(), `activating kit.archiving again changed the active tools to ${again.join()}`);
|
|
168
|
+
const activated = (yield* turn.published).flatMap((event) => event._tag === "skills.activated" ? [event] : []);
|
|
169
|
+
yield* expect("activation", activated.filter((event) => event.skills.includes("kit.archiving")).length === 1 && activated.some((event) => event.tools.includes(Archive.name)), `kit.archiving's activation published ${activated.length} skills.activated`);
|
|
170
|
+
const unknown = yield* Effect.flip(turn.tools.activate(["kit.no-such-skill"], "host"));
|
|
171
|
+
yield* expect("activation", unknown.code === "skills.unknown", `activating an unknown skill failed with ${unknown.code}`);
|
|
172
|
+
yield* Effect.result(turn.tools.activate(["kit.vault"], "host"));
|
|
173
|
+
const after = yield* turn.tools.active;
|
|
174
|
+
yield* expect("activation", !after.includes(Vault.name) && grown.every((tool) => after.includes(tool)), `after asking for a skill whose permission is not granted the active tools are ${after.join()}`);
|
|
175
|
+
}))),
|
|
176
|
+
check("a handled call publishes tool.started, its progress and tool.completed under one invocation id, minted after a refused call's, which its handler sees, on the run's bus only", "invocation", () => inTurn({}, (turn) => Effect.gen(function* () {
|
|
177
|
+
yield* turn.call(Archive.name, { id: "r0" });
|
|
178
|
+
const outcome = yield* turn.call(Report.name, {}, 2), id = `${RUN}:call:1`;
|
|
179
|
+
yield* expect("invocation", !outcome.isFailure && outcome.result === `${id}|${Report.name}|2|the opener's`, `the handler saw ${JSON.stringify(outcome.result)}, not its call ${id}|${Report.name}|2 and the services where the tools were opened`);
|
|
180
|
+
const events = (yield* turn.published).filter((event) => event._tag.startsWith("tool."));
|
|
181
|
+
yield* expect("invocation", tags(events) === "tool.refused,tool.started,tool.progress,tool.progress,tool.completed", `the two calls published ${tags(events)}`);
|
|
182
|
+
const handled = events.slice(1);
|
|
183
|
+
yield* expect("invocation", idOf(events[0]) === `${RUN}:call:0` && handled.every((event) => idOf(event) === id && ("tool" in event) && event.tool === Report.name && ("step" in event) && event.step === 2), `the calls' events carry the ids ${events.map(idOf).join()}`);
|
|
184
|
+
const completed = events.at(-1);
|
|
185
|
+
yield* expect("invocation", completed?._tag === "tool.completed" && completed.ok, "the handled call did not complete ok");
|
|
186
|
+
const stored = (yield* turn.stored).map((event) => event.kind).filter((kind) => kind.startsWith("tool."));
|
|
187
|
+
yield* expect("invocation", stored.join() === "tool.refused,tool.started,tool.completed", `the turn stored ${stored.join()} (a registry publishes on the run's bus and never writes the journal itself)`);
|
|
188
|
+
const next = yield* turn.call(Ping.name, { n: 1 }), started = (yield* turn.published).filter((event) => event._tag === "tool.started").map(idOf);
|
|
189
|
+
yield* expect("invocation", !next.isFailure && started.join() === `${id},${RUN}:call:2`, `the next call took the id ${started.at(-1) ?? "none"}`);
|
|
190
|
+
}))),
|
|
191
|
+
check("the turn's PermissionGrants, read where the tools are opened, let a skill that needs them be activated and its tools run", "grants", () => inTurn({ grants: ["kit.vault"] }, (turn) => Effect.gen(function* () {
|
|
192
|
+
yield* turn.tools.activate(["kit.vault"], "host");
|
|
193
|
+
const active = yield* turn.tools.active;
|
|
194
|
+
yield* expect("grants", active.includes(Vault.name), `with kit.vault granted, activating its skill left the active tools ${active.join()}`);
|
|
195
|
+
const outcome = yield* turn.call(Vault.name, { key: "k" });
|
|
196
|
+
yield* expect("grants", !outcome.isFailure && outcome.result === "secret k", `the granted call returned ${JSON.stringify(outcome.result)}`);
|
|
197
|
+
}))),
|
|
198
|
+
refusal("inactive", "refused-inactive", [[Archive.name, { id: "r1" }]]),
|
|
199
|
+
refusal("forbidden", "refused-forbidden", [[Vault.name, { key: "k" }]], { vaultActive: !0 }),
|
|
200
|
+
refusal("denied", "refused-denied", [[Ping.name, { n: 1 }]], {
|
|
201
|
+
policy: ActionPolicy.of({ authorize: (tool) => tool === Ping.name ? Effect.fail(new HarnessError({ code: "conformance.denied", message: "pings are closed today" })) : Effect.void })
|
|
202
|
+
}),
|
|
203
|
+
refusal("call-limit", "refused-call-limit", [[Ping.name, { n: 1 }], [Ping.name, { n: 2 }]], { maxCallsPerRun: 1 }),
|
|
204
|
+
refusal("tool-call-limit", "refused-tool-call-limit", [[Ping.name, { n: 1 }], [Ping.name, { n: 2 }], [Ping.name, { n: 3 }]])
|
|
205
|
+
];
|
|
206
|
+
};
|
package/dist/turn/turn-bus.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Cause, Context, Deferred, Effect, Fiber, Option, Queue, Ref } from "effect";
|
|
2
2
|
import { HarnessError } from "../harness/plugin.entity.js";
|
|
3
|
+
import { CurrentTraceContent, replayableContext } from "../loop/traceContent.js";
|
|
3
4
|
const failureOf = (cause) => Option.getOrElse(Cause.findErrorOption(cause), () => {
|
|
4
5
|
const defect = Cause.squash(cause);
|
|
5
6
|
return defect instanceof HarnessError ? defect : new HarnessError({ code: "events.background", message: Cause.pretty(cause).slice(0, 300) });
|
|
@@ -20,7 +21,7 @@ export const makeTurnEvents = (options) => Effect.gen(function* () {
|
|
|
20
21
|
}
|
|
21
22
|
if (Option.isSome(failure))
|
|
22
23
|
return;
|
|
23
|
-
yield* handle(item.event).pipe(Effect.provideService(depth, item.depth), Effect.catchCause((cause) => Ref.set(failed, Option.some(failureOf(cause)))));
|
|
24
|
+
yield* handle(item.event).pipe(Effect.provideService(depth, item.depth), Effect.provideService(CurrentTraceContent, item.traceContent), Effect.catchCause((cause) => Ref.set(failed, Option.some(failureOf(cause)))));
|
|
24
25
|
})));
|
|
25
26
|
yield* Effect.forkScoped(Effect.forever(take));
|
|
26
27
|
const deliver = (event) => Effect.gen(function* () {
|
|
@@ -28,11 +29,11 @@ export const makeTurnEvents = (options) => Effect.gen(function* () {
|
|
|
28
29
|
if (Option.isSome(failure))
|
|
29
30
|
return yield* Effect.fail(failure.value);
|
|
30
31
|
yield* Ref.update(queued, (value) => value + 1);
|
|
31
|
-
yield* Queue.offer(queue, { _tag: "Event", event, depth: yield* Effect.service(depth) });
|
|
32
|
+
yield* Queue.offer(queue, { _tag: "Event", event, depth: yield* Effect.service(depth), traceContent: yield* Effect.service(CurrentTraceContent) });
|
|
32
33
|
}), drain = Deferred.make().pipe(Effect.flatMap((done) => Queue.offer(queue, { _tag: "Marker", done }).pipe(Effect.andThen(Deferred.await(done)))));
|
|
33
34
|
return { deliver, drain };
|
|
34
35
|
}), subscribe = (select, handle, subscribeOptions) => Effect.gen(function* () {
|
|
35
|
-
const context = yield* Effect.context(), id = yield* Ref.getAndUpdate(nextId, (value) => value + 1), handled = (selected) => handle(selected).pipe(Effect.provide(context)), lane = (subscribeOptions?.mode ?? "inline") === "background" ? yield* background(handled, subscribeOptions?.capacity ?? defaultCapacity) : { deliver: handled, drain: Effect.void }, deliver = (event) => Option.match(select(event), {
|
|
36
|
+
const context = replayableContext(yield* Effect.context()), id = yield* Ref.getAndUpdate(nextId, (value) => value + 1), handled = (selected) => handle(selected).pipe(Effect.provide(context)), lane = (subscribeOptions?.mode ?? "inline") === "background" ? yield* background(handled, subscribeOptions?.capacity ?? defaultCapacity) : { deliver: handled, drain: Effect.void }, deliver = (event) => Option.match(select(event), {
|
|
36
37
|
onNone: () => Effect.void,
|
|
37
38
|
onSome: lane.deliver
|
|
38
39
|
});
|
|
@@ -9,18 +9,22 @@ export declare const CompletionVerdict: Schema.Struct<{
|
|
|
9
9
|
readonly facts: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
10
10
|
}>;
|
|
11
11
|
export type CompletionVerdict = typeof CompletionVerdict.Type;
|
|
12
|
-
export declare const TurnStartedEvent: Schema.
|
|
12
|
+
export declare const TurnStartedEvent: Schema.Struct<{
|
|
13
|
+
readonly _tag: Schema.tag<"turn.started">;
|
|
13
14
|
readonly runId: Schema.String;
|
|
14
15
|
readonly turn: Schema.Int;
|
|
15
16
|
readonly userMessage: typeof UserMessage;
|
|
17
|
+
readonly meta: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
18
|
+
readonly resumes: Schema.optionalKey<Schema.Int>;
|
|
16
19
|
}>;
|
|
17
|
-
export declare const StepStartedEvent: Schema.
|
|
20
|
+
export declare const StepStartedEvent: Schema.Struct<{
|
|
21
|
+
readonly _tag: Schema.tag<"step.started">;
|
|
18
22
|
readonly step: Schema.Int;
|
|
19
|
-
/** Step zero of a host-planned batch: no provider call. */
|
|
20
23
|
readonly planned: Schema.Boolean;
|
|
21
24
|
readonly activeTools: Schema.$Array<Schema.String>;
|
|
22
25
|
}>;
|
|
23
|
-
export declare const ToolStartedEvent: Schema.
|
|
26
|
+
export declare const ToolStartedEvent: Schema.Struct<{
|
|
27
|
+
readonly _tag: Schema.tag<"tool.started">;
|
|
24
28
|
readonly step: Schema.Int;
|
|
25
29
|
readonly invocationId: Schema.String;
|
|
26
30
|
readonly tool: Schema.String;
|
|
@@ -28,20 +32,45 @@ export declare const ToolStartedEvent: Schema.TaggedStruct<"tool.started", {
|
|
|
28
32
|
readonly labels: Schema.$Record<Schema.String, Schema.String>;
|
|
29
33
|
readonly stage: Schema.OptionFromNullOr<Schema.String>;
|
|
30
34
|
}>;
|
|
31
|
-
|
|
35
|
+
/** Why the tool registry refused a call before its handler ran. */
|
|
36
|
+
export declare const ToolRefusalReason: Schema.Literals<readonly ["inactive", "forbidden", "denied", "call-limit", "tool-call-limit"]>;
|
|
37
|
+
export type ToolRefusalReason = typeof ToolRefusalReason.Type;
|
|
38
|
+
export declare const ToolRefusedEvent: Schema.Struct<{
|
|
39
|
+
readonly _tag: Schema.tag<"tool.refused">;
|
|
40
|
+
readonly step: Schema.Int;
|
|
41
|
+
readonly invocationId: Schema.String;
|
|
42
|
+
readonly tool: Schema.String;
|
|
43
|
+
readonly input: Schema.Unknown;
|
|
44
|
+
readonly reason: Schema.Literals<readonly ["inactive", "forbidden", "denied", "call-limit", "tool-call-limit"]>;
|
|
45
|
+
readonly message: Schema.String;
|
|
46
|
+
}>;
|
|
47
|
+
export declare const ToolCompletedEvent: Schema.Struct<{
|
|
48
|
+
readonly _tag: Schema.tag<"tool.completed">;
|
|
32
49
|
readonly step: Schema.Int;
|
|
33
50
|
readonly invocationId: Schema.String;
|
|
34
51
|
readonly tool: Schema.String;
|
|
35
52
|
readonly input: Schema.Unknown;
|
|
36
53
|
readonly ok: Schema.Boolean;
|
|
37
|
-
/** The decoded value the handler returned (or failed with). Not journaled. */
|
|
38
54
|
readonly result: Schema.Unknown;
|
|
39
|
-
/** The wire form of `result`, as the model receives it. */
|
|
40
55
|
readonly encoded: Schema.Unknown;
|
|
41
56
|
readonly durationMs: Schema.Number;
|
|
42
57
|
readonly labels: Schema.$Record<Schema.String, Schema.String>;
|
|
43
58
|
readonly stage: Schema.OptionFromNullOr<Schema.String>;
|
|
44
59
|
}>;
|
|
60
|
+
/**
|
|
61
|
+
* What a running tool reports before it completes: a command's output as it
|
|
62
|
+
* arrives (`stdout`, `stderr`), or a step of a long job (`status`).
|
|
63
|
+
* Transient: shown live, never journaled; `tool.completed` carries the
|
|
64
|
+
* result. A handler publishes it with `publishToolProgress`.
|
|
65
|
+
*/
|
|
66
|
+
export declare const ToolProgressEvent: Schema.Struct<{
|
|
67
|
+
readonly _tag: Schema.tag<"tool.progress">;
|
|
68
|
+
readonly step: Schema.Int;
|
|
69
|
+
readonly invocationId: Schema.String;
|
|
70
|
+
readonly tool: Schema.String;
|
|
71
|
+
readonly channel: Schema.Literals<readonly ["stdout", "stderr", "status"]>;
|
|
72
|
+
readonly text: Schema.String;
|
|
73
|
+
}>;
|
|
45
74
|
export declare const StepResult: Schema.Struct<{
|
|
46
75
|
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
47
76
|
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
@@ -49,10 +78,10 @@ export declare const StepResult: Schema.Struct<{
|
|
|
49
78
|
readonly ok: Schema.Boolean;
|
|
50
79
|
}>;
|
|
51
80
|
export type StepResult = typeof StepResult.Type;
|
|
52
|
-
export declare const StepEndedEvent: Schema.
|
|
81
|
+
export declare const StepEndedEvent: Schema.Struct<{
|
|
82
|
+
readonly _tag: Schema.tag<"step.ended">;
|
|
53
83
|
readonly step: Schema.Int;
|
|
54
84
|
readonly status: Schema.Literals<readonly ["completed", "failed", "cancelled"]>;
|
|
55
|
-
/** The step's recorded tool results, in log order. */
|
|
56
85
|
readonly results: Schema.$Array<Schema.Struct<{
|
|
57
86
|
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
58
87
|
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
@@ -60,7 +89,8 @@ export declare const StepEndedEvent: Schema.TaggedStruct<"step.ended", {
|
|
|
60
89
|
readonly ok: Schema.Boolean;
|
|
61
90
|
}>>;
|
|
62
91
|
}>;
|
|
63
|
-
export declare const CompletionEvaluatedEvent: Schema.
|
|
92
|
+
export declare const CompletionEvaluatedEvent: Schema.Struct<{
|
|
93
|
+
readonly _tag: Schema.tag<"completion.evaluated">;
|
|
64
94
|
readonly step: Schema.Int;
|
|
65
95
|
readonly verdict: Schema.Struct<{
|
|
66
96
|
readonly complete: Schema.Boolean;
|
|
@@ -70,12 +100,14 @@ export declare const CompletionEvaluatedEvent: Schema.TaggedStruct<"completion.e
|
|
|
70
100
|
readonly facts: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
71
101
|
}>;
|
|
72
102
|
}>;
|
|
73
|
-
export declare const SkillsActivatedEvent: Schema.
|
|
103
|
+
export declare const SkillsActivatedEvent: Schema.Struct<{
|
|
104
|
+
readonly _tag: Schema.tag<"skills.activated">;
|
|
74
105
|
readonly skills: Schema.$Array<Schema.String>;
|
|
75
106
|
readonly tools: Schema.$Array<Schema.String>;
|
|
76
107
|
readonly source: Schema.Literals<readonly ["always", "matcher", "load_skill", "host"]>;
|
|
77
108
|
}>;
|
|
78
|
-
export declare const ContextBuiltEvent: Schema.
|
|
109
|
+
export declare const ContextBuiltEvent: Schema.Struct<{
|
|
110
|
+
readonly _tag: Schema.tag<"context.built">;
|
|
79
111
|
readonly step: Schema.Int;
|
|
80
112
|
readonly turn: Schema.Int;
|
|
81
113
|
readonly strategy: Schema.String;
|
|
@@ -84,10 +116,12 @@ export declare const ContextBuiltEvent: Schema.TaggedStruct<"context.built", {
|
|
|
84
116
|
readonly systemFingerprint: Schema.String;
|
|
85
117
|
readonly estimatedTokens: Schema.Int;
|
|
86
118
|
readonly reservedTokens: Schema.Int;
|
|
119
|
+
readonly budgetTokens: Schema.Int;
|
|
87
120
|
readonly compactions: Schema.Int;
|
|
88
121
|
readonly activeTools: Schema.$Array<Schema.String>;
|
|
89
122
|
}>;
|
|
90
|
-
export declare const DecisionRecordedEvent: Schema.
|
|
123
|
+
export declare const DecisionRecordedEvent: Schema.Struct<{
|
|
124
|
+
readonly _tag: Schema.tag<"decision.recorded">;
|
|
91
125
|
readonly record: Schema.Struct<{
|
|
92
126
|
readonly version: Schema.Literal<1>;
|
|
93
127
|
readonly id: Schema.brand<Schema.Trimmed, "DecisionId">;
|
|
@@ -107,7 +141,8 @@ export declare const DecisionRecordedEvent: Schema.TaggedStruct<"decision.record
|
|
|
107
141
|
readonly probabilities: Schema.OptionFromNullOr<Schema.$Record<Schema.String, Schema.Number>>;
|
|
108
142
|
}>;
|
|
109
143
|
}>;
|
|
110
|
-
export declare const AssistantMessageEvent: Schema.
|
|
144
|
+
export declare const AssistantMessageEvent: Schema.Struct<{
|
|
145
|
+
readonly _tag: Schema.tag<"assistant.message">;
|
|
111
146
|
readonly step: Schema.Int;
|
|
112
147
|
readonly text: Schema.String;
|
|
113
148
|
readonly reasoning: Schema.String;
|
|
@@ -122,66 +157,103 @@ export declare const AssistantMessageEvent: Schema.TaggedStruct<"assistant.messa
|
|
|
122
157
|
readonly outputTokens: Schema.Number;
|
|
123
158
|
readonly totalTokens: Schema.Number;
|
|
124
159
|
readonly cacheReadTokens: Schema.Number;
|
|
160
|
+
readonly cacheWriteTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
161
|
+
readonly reasoningTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
125
162
|
}>;
|
|
126
163
|
}>;
|
|
127
164
|
/** A streamed increment — transient: never journaled, restated by assistant.message. */
|
|
128
|
-
export declare const AssistantDeltaEvent: Schema.
|
|
165
|
+
export declare const AssistantDeltaEvent: Schema.Struct<{
|
|
166
|
+
readonly _tag: Schema.tag<"assistant.delta">;
|
|
129
167
|
readonly step: Schema.Int;
|
|
130
168
|
readonly channel: Schema.Literals<readonly ["text", "reasoning", "tool-params"]>;
|
|
131
169
|
readonly id: Schema.String;
|
|
132
170
|
readonly delta: Schema.String;
|
|
133
171
|
}>;
|
|
134
|
-
export declare const
|
|
172
|
+
export declare const StreamFallbackEvent: Schema.Struct<{
|
|
173
|
+
readonly _tag: Schema.tag<"stream.fallback">;
|
|
174
|
+
readonly step: Schema.Int;
|
|
175
|
+
readonly message: Schema.String;
|
|
176
|
+
}>;
|
|
177
|
+
export declare const TurnEndedEvent: Schema.Struct<{
|
|
178
|
+
readonly _tag: Schema.tag<"turn.ended">;
|
|
135
179
|
readonly runId: Schema.String;
|
|
136
180
|
readonly turn: Schema.Int;
|
|
137
181
|
readonly outcome: Schema.Literals<readonly ["completed", "partial", "failed"]>;
|
|
138
182
|
readonly reply: Schema.OptionFromNullOr<Schema.String>;
|
|
183
|
+
readonly usage: Schema.Struct<{
|
|
184
|
+
readonly inputTokens: Schema.Number;
|
|
185
|
+
readonly outputTokens: Schema.Number;
|
|
186
|
+
readonly totalTokens: Schema.Number;
|
|
187
|
+
readonly cacheReadTokens: Schema.Number;
|
|
188
|
+
readonly cacheWriteTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
189
|
+
readonly reasoningTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
190
|
+
}>;
|
|
139
191
|
}>;
|
|
140
192
|
/** An application's own event (see `defineHostEvent`); `name` is the host's namespace. */
|
|
141
|
-
export declare const HostEvent: Schema.
|
|
193
|
+
export declare const HostEvent: Schema.Struct<{
|
|
194
|
+
readonly _tag: Schema.tag<"host">;
|
|
142
195
|
readonly name: Schema.Trimmed;
|
|
143
196
|
readonly data: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
144
197
|
}>;
|
|
145
|
-
export declare const TurnEvent: Schema.Union<readonly [Schema.
|
|
198
|
+
export declare const TurnEvent: Schema.Union<readonly [Schema.Struct<{
|
|
199
|
+
readonly _tag: Schema.tag<"turn.started">;
|
|
146
200
|
readonly runId: Schema.String;
|
|
147
201
|
readonly turn: Schema.Int;
|
|
148
202
|
readonly userMessage: typeof UserMessage;
|
|
149
|
-
|
|
203
|
+
readonly meta: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
204
|
+
readonly resumes: Schema.optionalKey<Schema.Int>;
|
|
205
|
+
}>, Schema.Struct<{
|
|
206
|
+
readonly _tag: Schema.tag<"step.started">;
|
|
150
207
|
readonly step: Schema.Int;
|
|
151
|
-
/** Step zero of a host-planned batch: no provider call. */
|
|
152
208
|
readonly planned: Schema.Boolean;
|
|
153
209
|
readonly activeTools: Schema.$Array<Schema.String>;
|
|
154
|
-
}>, Schema.
|
|
210
|
+
}>, Schema.Struct<{
|
|
211
|
+
readonly _tag: Schema.tag<"tool.started">;
|
|
155
212
|
readonly step: Schema.Int;
|
|
156
213
|
readonly invocationId: Schema.String;
|
|
157
214
|
readonly tool: Schema.String;
|
|
158
215
|
readonly input: Schema.Unknown;
|
|
159
216
|
readonly labels: Schema.$Record<Schema.String, Schema.String>;
|
|
160
217
|
readonly stage: Schema.OptionFromNullOr<Schema.String>;
|
|
161
|
-
}>, Schema.
|
|
218
|
+
}>, Schema.Struct<{
|
|
219
|
+
readonly _tag: Schema.tag<"tool.refused">;
|
|
220
|
+
readonly step: Schema.Int;
|
|
221
|
+
readonly invocationId: Schema.String;
|
|
222
|
+
readonly tool: Schema.String;
|
|
223
|
+
readonly input: Schema.Unknown;
|
|
224
|
+
readonly reason: Schema.Literals<readonly ["inactive", "forbidden", "denied", "call-limit", "tool-call-limit"]>;
|
|
225
|
+
readonly message: Schema.String;
|
|
226
|
+
}>, Schema.Struct<{
|
|
227
|
+
readonly _tag: Schema.tag<"tool.progress">;
|
|
228
|
+
readonly step: Schema.Int;
|
|
229
|
+
readonly invocationId: Schema.String;
|
|
230
|
+
readonly tool: Schema.String;
|
|
231
|
+
readonly channel: Schema.Literals<readonly ["stdout", "stderr", "status"]>;
|
|
232
|
+
readonly text: Schema.String;
|
|
233
|
+
}>, Schema.Struct<{
|
|
234
|
+
readonly _tag: Schema.tag<"tool.completed">;
|
|
162
235
|
readonly step: Schema.Int;
|
|
163
236
|
readonly invocationId: Schema.String;
|
|
164
237
|
readonly tool: Schema.String;
|
|
165
238
|
readonly input: Schema.Unknown;
|
|
166
239
|
readonly ok: Schema.Boolean;
|
|
167
|
-
/** The decoded value the handler returned (or failed with). Not journaled. */
|
|
168
240
|
readonly result: Schema.Unknown;
|
|
169
|
-
/** The wire form of `result`, as the model receives it. */
|
|
170
241
|
readonly encoded: Schema.Unknown;
|
|
171
242
|
readonly durationMs: Schema.Number;
|
|
172
243
|
readonly labels: Schema.$Record<Schema.String, Schema.String>;
|
|
173
244
|
readonly stage: Schema.OptionFromNullOr<Schema.String>;
|
|
174
|
-
}>, Schema.
|
|
245
|
+
}>, Schema.Struct<{
|
|
246
|
+
readonly _tag: Schema.tag<"step.ended">;
|
|
175
247
|
readonly step: Schema.Int;
|
|
176
248
|
readonly status: Schema.Literals<readonly ["completed", "failed", "cancelled"]>;
|
|
177
|
-
/** The step's recorded tool results, in log order. */
|
|
178
249
|
readonly results: Schema.$Array<Schema.Struct<{
|
|
179
250
|
readonly entry: Schema.brand<Schema.Trimmed, "EntryId">;
|
|
180
251
|
readonly toolCallId: Schema.brand<Schema.String, "ToolCallId">;
|
|
181
252
|
readonly tool: Schema.String;
|
|
182
253
|
readonly ok: Schema.Boolean;
|
|
183
254
|
}>>;
|
|
184
|
-
}>, Schema.
|
|
255
|
+
}>, Schema.Struct<{
|
|
256
|
+
readonly _tag: Schema.tag<"completion.evaluated">;
|
|
185
257
|
readonly step: Schema.Int;
|
|
186
258
|
readonly verdict: Schema.Struct<{
|
|
187
259
|
readonly complete: Schema.Boolean;
|
|
@@ -190,11 +262,13 @@ export declare const TurnEvent: Schema.Union<readonly [Schema.TaggedStruct<"turn
|
|
|
190
262
|
/** Host facts behind the verdict, journaled with it. */
|
|
191
263
|
readonly facts: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
192
264
|
}>;
|
|
193
|
-
}>, Schema.
|
|
265
|
+
}>, Schema.Struct<{
|
|
266
|
+
readonly _tag: Schema.tag<"skills.activated">;
|
|
194
267
|
readonly skills: Schema.$Array<Schema.String>;
|
|
195
268
|
readonly tools: Schema.$Array<Schema.String>;
|
|
196
269
|
readonly source: Schema.Literals<readonly ["always", "matcher", "load_skill", "host"]>;
|
|
197
|
-
}>, Schema.
|
|
270
|
+
}>, Schema.Struct<{
|
|
271
|
+
readonly _tag: Schema.tag<"context.built">;
|
|
198
272
|
readonly step: Schema.Int;
|
|
199
273
|
readonly turn: Schema.Int;
|
|
200
274
|
readonly strategy: Schema.String;
|
|
@@ -203,9 +277,11 @@ export declare const TurnEvent: Schema.Union<readonly [Schema.TaggedStruct<"turn
|
|
|
203
277
|
readonly systemFingerprint: Schema.String;
|
|
204
278
|
readonly estimatedTokens: Schema.Int;
|
|
205
279
|
readonly reservedTokens: Schema.Int;
|
|
280
|
+
readonly budgetTokens: Schema.Int;
|
|
206
281
|
readonly compactions: Schema.Int;
|
|
207
282
|
readonly activeTools: Schema.$Array<Schema.String>;
|
|
208
|
-
}>, Schema.
|
|
283
|
+
}>, Schema.Struct<{
|
|
284
|
+
readonly _tag: Schema.tag<"decision.recorded">;
|
|
209
285
|
readonly record: Schema.Struct<{
|
|
210
286
|
readonly version: Schema.Literal<1>;
|
|
211
287
|
readonly id: Schema.brand<Schema.Trimmed, "DecisionId">;
|
|
@@ -224,7 +300,8 @@ export declare const TurnEvent: Schema.Union<readonly [Schema.TaggedStruct<"turn
|
|
|
224
300
|
readonly applied: Schema.OptionFromNullOr<Schema.String>;
|
|
225
301
|
readonly probabilities: Schema.OptionFromNullOr<Schema.$Record<Schema.String, Schema.Number>>;
|
|
226
302
|
}>;
|
|
227
|
-
}>, Schema.
|
|
303
|
+
}>, Schema.Struct<{
|
|
304
|
+
readonly _tag: Schema.tag<"assistant.message">;
|
|
228
305
|
readonly step: Schema.Int;
|
|
229
306
|
readonly text: Schema.String;
|
|
230
307
|
readonly reasoning: Schema.String;
|
|
@@ -239,18 +316,35 @@ export declare const TurnEvent: Schema.Union<readonly [Schema.TaggedStruct<"turn
|
|
|
239
316
|
readonly outputTokens: Schema.Number;
|
|
240
317
|
readonly totalTokens: Schema.Number;
|
|
241
318
|
readonly cacheReadTokens: Schema.Number;
|
|
319
|
+
readonly cacheWriteTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
320
|
+
readonly reasoningTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
242
321
|
}>;
|
|
243
|
-
}>, Schema.
|
|
322
|
+
}>, Schema.Struct<{
|
|
323
|
+
readonly _tag: Schema.tag<"assistant.delta">;
|
|
244
324
|
readonly step: Schema.Int;
|
|
245
325
|
readonly channel: Schema.Literals<readonly ["text", "reasoning", "tool-params"]>;
|
|
246
326
|
readonly id: Schema.String;
|
|
247
327
|
readonly delta: Schema.String;
|
|
248
|
-
}>, Schema.
|
|
328
|
+
}>, Schema.Struct<{
|
|
329
|
+
readonly _tag: Schema.tag<"stream.fallback">;
|
|
330
|
+
readonly step: Schema.Int;
|
|
331
|
+
readonly message: Schema.String;
|
|
332
|
+
}>, Schema.Struct<{
|
|
333
|
+
readonly _tag: Schema.tag<"turn.ended">;
|
|
249
334
|
readonly runId: Schema.String;
|
|
250
335
|
readonly turn: Schema.Int;
|
|
251
336
|
readonly outcome: Schema.Literals<readonly ["completed", "partial", "failed"]>;
|
|
252
337
|
readonly reply: Schema.OptionFromNullOr<Schema.String>;
|
|
253
|
-
|
|
338
|
+
readonly usage: Schema.Struct<{
|
|
339
|
+
readonly inputTokens: Schema.Number;
|
|
340
|
+
readonly outputTokens: Schema.Number;
|
|
341
|
+
readonly totalTokens: Schema.Number;
|
|
342
|
+
readonly cacheReadTokens: Schema.Number;
|
|
343
|
+
readonly cacheWriteTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
344
|
+
readonly reasoningTokens: Schema.withDecodingDefaultKey<Schema.Number, never>;
|
|
345
|
+
}>;
|
|
346
|
+
}>, Schema.Struct<{
|
|
347
|
+
readonly _tag: Schema.tag<"host">;
|
|
254
348
|
readonly name: Schema.Trimmed;
|
|
255
349
|
readonly data: Schema.$Record<Schema.String, Schema.Unknown>;
|
|
256
350
|
}>]>;
|
|
@@ -261,3 +355,9 @@ export type TurnEventOf<Name extends TurnEventName> = Extract<TurnEvent, {
|
|
|
261
355
|
}>;
|
|
262
356
|
/** Events that exist only for live rendering and are never journaled. */
|
|
263
357
|
export declare const TransientTurnEvents: ReadonlyArray<TurnEventName>;
|
|
358
|
+
/**
|
|
359
|
+
* Every bus event's name, in the union's order. All of them are reserved
|
|
360
|
+
* (`RESERVED_KINDS`): a host event may not take one, whether the journal
|
|
361
|
+
* stores the bus event under that name or not.
|
|
362
|
+
*/
|
|
363
|
+
export declare const TURN_EVENT_NAMES: ReadonlyArray<TurnEventName>;
|
|
@@ -33,3 +33,14 @@ export declare const defineHostEvent: <A, I extends Readonly<Record<string, unkn
|
|
|
33
33
|
publish: (events: TurnEventsService, data: A) => Effect.Effect<void, HarnessError, never>;
|
|
34
34
|
on: <R>(handle: (data: A) => Effect.Effect<void, HarnessError, R>, options?: SubscribeOptions) => Subscription<R>;
|
|
35
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Report progress from inside a tool handler: publishes a transient
|
|
38
|
+
* `tool.progress` with the call's step, invocation id and tool name (from
|
|
39
|
+
* `CurrentToolInvocation`) on the run's bus. `channel` is `stdout` or
|
|
40
|
+
* `stderr` for a command's output as it arrives, `status` for anything else.
|
|
41
|
+
* Outside a tool call, or without a `RunContext`, it does nothing. As for
|
|
42
|
+
* `tool.started`, a subscriber failure fails the turn: the handler stops
|
|
43
|
+
* there, and the tool registry fails the turn after `tool.completed`
|
|
44
|
+
* instead of handing the failure to the model as the tool's result.
|
|
45
|
+
*/
|
|
46
|
+
export declare const publishToolProgress: (channel: TurnEventOf<"tool.progress">["channel"], text: string) => Effect.Effect<void>;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Effect, Option, Schema } from "effect";
|
|
2
2
|
import { HarnessError } from "../harness/plugin.entity.js";
|
|
3
|
+
import { CurrentToolInvocation } from "../loop/stepContext.js";
|
|
4
|
+
import { RunContext } from "../ports/run-context.port.js";
|
|
3
5
|
const isNamed = (name) => (event) => event._tag === name;
|
|
4
6
|
export const onEvent = (name, handle, options) => (events) => events.subscribe((event) => Option.liftPredicate(event, isNamed(name)), handle, options), onTool = (tool, handle, options) => {
|
|
5
7
|
const isInput = Schema.is(tool.parametersSchema), isResult = Schema.is(tool.successSchema);
|
|
@@ -18,4 +20,10 @@ export const onEvent = (name, handle, options) => (events) => events.subscribe((
|
|
|
18
20
|
publish: (events, data) => make(data).pipe(Effect.flatMap(events.publish)),
|
|
19
21
|
on: (handle, options) => (events) => events.subscribe(select, handle, options)
|
|
20
22
|
};
|
|
21
|
-
}
|
|
23
|
+
}, publishToolProgress = (channel, text) => Effect.gen(function* () {
|
|
24
|
+
const invocation = yield* Effect.service(CurrentToolInvocation), run = yield* Effect.serviceOption(RunContext);
|
|
25
|
+
if (Option.isNone(invocation) || Option.isNone(run))
|
|
26
|
+
return;
|
|
27
|
+
const { step, invocationId, tool } = invocation.value;
|
|
28
|
+
yield* run.value.events.publish({ _tag: "tool.progress", step, invocationId, tool, channel, text }).pipe(Effect.orDie);
|
|
29
|
+
});
|