@xandreed/core 0.3.0 → 0.8.0-next.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 +31 -0
- 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 +24 -0
- 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 +48 -4
- package/dist/index.js +46 -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 +2 -3
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
import { AgentMessage, ToolCallId } from "../domain/message.entity.js";
|
|
3
|
+
import { DecisionId } from "../decision-record.entity.js";
|
|
4
|
+
import { UserMessageFromString } from "../turn/user-message.entity.js";
|
|
5
|
+
export const EntryId = Schema.Trimmed.check(Schema.isNonEmpty()).pipe(Schema.brand("EntryId")), Subject = Schema.Struct({
|
|
6
|
+
kind: Schema.Trimmed.check(Schema.isNonEmpty()),
|
|
7
|
+
id: Schema.Trimmed.check(Schema.isNonEmpty()),
|
|
8
|
+
label: Schema.OptionFromNullOr(Schema.String),
|
|
9
|
+
data: Schema.OptionFromNullOr(Schema.Unknown)
|
|
10
|
+
}), ArtifactRef = Schema.Struct({
|
|
11
|
+
id: Schema.Trimmed.check(Schema.isNonEmpty()),
|
|
12
|
+
kind: Schema.Literals(["image", "file"]),
|
|
13
|
+
mediaType: Schema.Trimmed.check(Schema.isNonEmpty()),
|
|
14
|
+
url: Schema.String,
|
|
15
|
+
alt: Schema.OptionFromNullOr(Schema.String)
|
|
16
|
+
}), CompactionAction = Schema.Union([
|
|
17
|
+
Schema.TaggedStruct("CompactViews", { entries: Schema.Array(EntryId), texts: Schema.Array(Schema.String) }),
|
|
18
|
+
Schema.TaggedStruct("Spill", { entry: EntryId, preview: Schema.String }),
|
|
19
|
+
Schema.TaggedStruct("DropTurns", { throughTurn: Schema.Int, ledger: Schema.String }),
|
|
20
|
+
Schema.TaggedStruct("Summarize", { keepFromTurn: Schema.Int, summary: Schema.String })
|
|
21
|
+
]), ActivationSource = Schema.Literals(["always", "matcher", "load_skill", "host"]), LogBody = Schema.Union([
|
|
22
|
+
Schema.TaggedStruct("SystemPrepared", {
|
|
23
|
+
fingerprint: Schema.String,
|
|
24
|
+
text: Schema.String,
|
|
25
|
+
sections: Schema.Array(Schema.Struct({ id: Schema.String, version: Schema.String, fingerprint: Schema.String }))
|
|
26
|
+
}),
|
|
27
|
+
Schema.TaggedStruct("TurnStarted", { userMessage: UserMessageFromString }).pipe(Schema.encodeKeys({ userMessage: "prompt" })),
|
|
28
|
+
Schema.TaggedStruct("TurnContext", { sectionId: Schema.String, version: Schema.String, text: Schema.String }),
|
|
29
|
+
Schema.TaggedStruct("Message", { message: AgentMessage }),
|
|
30
|
+
Schema.TaggedStruct("ToolResult", {
|
|
31
|
+
toolCallId: ToolCallId,
|
|
32
|
+
toolName: Schema.String,
|
|
33
|
+
isError: Schema.Boolean,
|
|
34
|
+
encoded: Schema.Unknown,
|
|
35
|
+
view: Schema.String,
|
|
36
|
+
viewVersion: Schema.String,
|
|
37
|
+
subjects: Schema.Array(Subject),
|
|
38
|
+
artifacts: Schema.Array(ArtifactRef),
|
|
39
|
+
pinned: Schema.Boolean
|
|
40
|
+
}),
|
|
41
|
+
Schema.TaggedStruct("ToolDigest", {
|
|
42
|
+
entry: EntryId,
|
|
43
|
+
version: Schema.String,
|
|
44
|
+
mode: Schema.Literals(["select", "summarize"]),
|
|
45
|
+
keep: Schema.Array(Schema.String),
|
|
46
|
+
text: Schema.String,
|
|
47
|
+
digester: Schema.String,
|
|
48
|
+
trigger: Schema.Literals(["write", "compaction"])
|
|
49
|
+
}),
|
|
50
|
+
Schema.TaggedStruct("StepContext", { step: Schema.Int, text: Schema.String }),
|
|
51
|
+
Schema.TaggedStruct("ToolsActivated", {
|
|
52
|
+
skills: Schema.Array(Schema.String),
|
|
53
|
+
tools: Schema.Array(Schema.String),
|
|
54
|
+
source: ActivationSource,
|
|
55
|
+
decision: Schema.OptionFromNullOr(DecisionId)
|
|
56
|
+
}),
|
|
57
|
+
Schema.TaggedStruct("Compaction", { strategy: Schema.String, version: Schema.String, action: CompactionAction }),
|
|
58
|
+
Schema.TaggedStruct("TurnEnded", {
|
|
59
|
+
outcome: Schema.Literals(["completed", "partial", "failed"]),
|
|
60
|
+
reply: Schema.OptionFromNullOr(Schema.String)
|
|
61
|
+
})
|
|
62
|
+
]), LogEntry = Schema.Struct({
|
|
63
|
+
id: EntryId,
|
|
64
|
+
runId: Schema.String,
|
|
65
|
+
turn: Schema.Int,
|
|
66
|
+
step: Schema.Int,
|
|
67
|
+
at: Schema.Number,
|
|
68
|
+
body: LogBody
|
|
69
|
+
}), BuiltContext = Schema.Struct({
|
|
70
|
+
messages: Schema.Array(AgentMessage),
|
|
71
|
+
fingerprint: Schema.String,
|
|
72
|
+
estimatedTokens: Schema.Int,
|
|
73
|
+
compactions: Schema.Array(EntryId),
|
|
74
|
+
through: Schema.OptionFromNullOr(EntryId)
|
|
75
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import type { AgentMessage } from "../domain/message.entity.js";
|
|
3
|
+
import type { HarnessError } from "../harness/plugin.entity.js";
|
|
4
|
+
import type { LogHandle, MaintainSignal, MemoryReader, MemorySession } from "../ports/memory.port.js";
|
|
5
|
+
import type { CompactionAction, EntryId, LogEntry } from "./memory-log.entity.js";
|
|
6
|
+
import type { RenderOptions } from "./memory-log.entity.functions.js";
|
|
7
|
+
/** What a strategy decides at one maintain point. */
|
|
8
|
+
export interface MaintainDecision {
|
|
9
|
+
readonly actions: ReadonlyArray<CompactionAction>;
|
|
10
|
+
/** Tool-result entries to digest now (recorded with trigger "compaction"). */
|
|
11
|
+
readonly digest: ReadonlyArray<EntryId>;
|
|
12
|
+
}
|
|
13
|
+
export declare const noMaintenance: MaintainDecision;
|
|
14
|
+
/** What distinguishes one memory strategy from another. */
|
|
15
|
+
export interface MemoryPolicy {
|
|
16
|
+
readonly strategy: {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly version: string;
|
|
19
|
+
};
|
|
20
|
+
readonly render: Pick<RenderOptions, "turnContext" | "replies" | "digests" | "media">;
|
|
21
|
+
/** Digest a result as it is written; None never digests at write time. */
|
|
22
|
+
readonly digestOnWrite: Option.Option<(result: {
|
|
23
|
+
readonly tool: string;
|
|
24
|
+
readonly chars: number;
|
|
25
|
+
}) => boolean>;
|
|
26
|
+
/** Digest calls run at once (default 4); results are recorded in log order either way. */
|
|
27
|
+
readonly digestConcurrency?: number;
|
|
28
|
+
/** Decide compactions and digests from the full log; the session records them before the next build. */
|
|
29
|
+
readonly maintain: (input: {
|
|
30
|
+
readonly entries: ReadonlyArray<LogEntry>;
|
|
31
|
+
readonly signal: MaintainSignal;
|
|
32
|
+
readonly turn: number;
|
|
33
|
+
readonly runId: string;
|
|
34
|
+
/** The render the strategy would send now (its own compactions applied). */
|
|
35
|
+
readonly render: (entries: ReadonlyArray<LogEntry>) => ReadonlyArray<AgentMessage>;
|
|
36
|
+
}) => Effect.Effect<MaintainDecision, HarnessError>;
|
|
37
|
+
}
|
|
38
|
+
/** The read-only view of a session, as tools, sections, matchers and reactions get it. */
|
|
39
|
+
export declare const readerOf: (session: MemorySession) => MemoryReader;
|
|
40
|
+
/**
|
|
41
|
+
* The shared session over a log handle. It loads the log once, assigns
|
|
42
|
+
* entry ids (`<runId>:<n>`), appends through to storage, runs the tools'
|
|
43
|
+
* digests the strategy asks for (with the `ResultDigester` of the environment
|
|
44
|
+
* it is opened in, when there is one, for the latest user message: a log
|
|
45
|
+
* without one digests nothing), and renders with the strategy's own compactions — so
|
|
46
|
+
* every strategy stores, retrieves and rebuilds the same way and differs only
|
|
47
|
+
* in the decisions it records.
|
|
48
|
+
*/
|
|
49
|
+
export declare const openLogSession: (log: LogHandle, policy: MemoryPolicy, scope: {
|
|
50
|
+
readonly runId: string;
|
|
51
|
+
}) => Effect.Effect<MemorySession, HarnessError>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Clock, Effect, Option, Ref, Semaphore } from "effect";
|
|
2
|
+
import { ResultDigester } from "../ports/memory.port.js";
|
|
3
|
+
import {
|
|
4
|
+
buildContext,
|
|
5
|
+
currentTurnOf,
|
|
6
|
+
entryId,
|
|
7
|
+
queryLog,
|
|
8
|
+
rawTranscript,
|
|
9
|
+
referenceTranscript,
|
|
10
|
+
renderLog,
|
|
11
|
+
subjectsOf,
|
|
12
|
+
toolInputsOf
|
|
13
|
+
} from "./memory-log.entity.functions.js";
|
|
14
|
+
export const noMaintenance = { actions: [], digest: [] };
|
|
15
|
+
const defaultDigestConcurrency = 4;
|
|
16
|
+
export const readerOf = (session) => ({
|
|
17
|
+
turn: session.turn,
|
|
18
|
+
entries: session.entries,
|
|
19
|
+
query: session.query,
|
|
20
|
+
subjects: session.subjects,
|
|
21
|
+
resolve: session.resolve,
|
|
22
|
+
transcript: session.transcript
|
|
23
|
+
});
|
|
24
|
+
const latestUserMessage = (entries) => Option.fromNullishOr(entries.flatMap((entry) => entry.body._tag === "TurnStarted" ? [entry.body.userMessage] : []).at(-1));
|
|
25
|
+
export const openLogSession = (log, policy, scope) => Effect.gen(function* () {
|
|
26
|
+
const runId = scope.runId, digester = yield* Effect.serviceOption(ResultDigester), stored = yield* log.read, entries = yield* Ref.make(stored), counter = yield* Ref.make(stored.filter((entry) => entry.runId === runId).length), writing = yield* Semaphore.make(1), renderOptions = (all, stepContext) => ({
|
|
27
|
+
...policy.render,
|
|
28
|
+
stepContext,
|
|
29
|
+
strategy: policy.strategy.id,
|
|
30
|
+
currentTurn: currentTurnOf(all),
|
|
31
|
+
currentRun: runId
|
|
32
|
+
}), record = (bodies, step) => bodies.length === 0 ? Effect.succeed([]) : writing.withPermits(1)(Effect.gen(function* () {
|
|
33
|
+
const current = currentTurnOf(yield* Ref.get(entries)), turn = bodies.some((body) => body._tag === "TurnStarted") ? current + 1 : Math.max(current, 1), at = yield* Clock.currentTimeMillis, start = yield* Ref.get(counter), appended = bodies.map((body, index) => ({ id: entryId(runId, start + index), runId, turn, step, at, body }));
|
|
34
|
+
yield* log.append(appended);
|
|
35
|
+
yield* Ref.set(counter, start + bodies.length);
|
|
36
|
+
yield* Ref.update(entries, (all) => [...all, ...appended]);
|
|
37
|
+
return appended;
|
|
38
|
+
})), digest = (targets, views, trigger, step) => Ref.get(entries).pipe(Effect.flatMap((all) => Option.match(Option.all([digester, latestUserMessage(all)]), {
|
|
39
|
+
onNone: () => Effect.succeed([]),
|
|
40
|
+
onSome: ([service, userMessage]) => Effect.gen(function* () {
|
|
41
|
+
const inputs = toolInputsOf(all, []), bodies = yield* Effect.forEach(targets, (entry) => entry.body._tag !== "ToolResult" || entry.body.isError ? Effect.succeed([]) : views.digest(entry.body.toolName, entry.body.encoded, inputs.get(String(entry.body.toolCallId)) ?? {}, userMessage).pipe(Effect.flatMap(Option.match({
|
|
42
|
+
onNone: () => Effect.succeed([]),
|
|
43
|
+
onSome: (task) => service.digest(task).pipe(Effect.map((outcome) => Option.toArray(Option.map(task.apply(outcome), (text) => ({
|
|
44
|
+
_tag: "ToolDigest",
|
|
45
|
+
entry: entry.id,
|
|
46
|
+
version: task.version,
|
|
47
|
+
mode: task.mode,
|
|
48
|
+
keep: outcome.keep,
|
|
49
|
+
text,
|
|
50
|
+
digester: `${service.id}@${service.version}`,
|
|
51
|
+
trigger
|
|
52
|
+
})))), Effect.orElseSucceed(() => []))
|
|
53
|
+
}))), { concurrency: policy.digestConcurrency ?? defaultDigestConcurrency });
|
|
54
|
+
return yield* record(bodies.flat(), step);
|
|
55
|
+
})
|
|
56
|
+
}))), recordTail = (tail, views, step) => Effect.gen(function* () {
|
|
57
|
+
const inputs = toolInputsOf(yield* Ref.get(entries), tail), bodies = yield* Effect.forEach(tail, (message) => message.role !== "tool" ? Effect.succeed([{ _tag: "Message", message }]) : Effect.forEach(message.content, (part) => {
|
|
58
|
+
const params = inputs.get(String(part.toolCallId)) ?? {};
|
|
59
|
+
return views.view(part.toolName, part.output, params, part.isError ?? !1).pipe(Effect.map((view) => ({
|
|
60
|
+
_tag: "ToolResult",
|
|
61
|
+
toolCallId: part.toolCallId,
|
|
62
|
+
toolName: part.toolName,
|
|
63
|
+
isError: part.isError ?? !1,
|
|
64
|
+
encoded: part.output,
|
|
65
|
+
view: view.text,
|
|
66
|
+
viewVersion: view.version,
|
|
67
|
+
subjects: view.subjects,
|
|
68
|
+
artifacts: view.artifacts,
|
|
69
|
+
pinned: view.pinned
|
|
70
|
+
})));
|
|
71
|
+
})), recorded = yield* record(bodies.flat(), step), onWrite = policy.digestOnWrite, targets = Option.isNone(onWrite) ? [] : recorded.filter((entry) => entry.body._tag === "ToolResult" && onWrite.value({ tool: entry.body.toolName, chars: entry.body.view.length })), digests = yield* digest(targets, views, "write", step);
|
|
72
|
+
return [...recorded, ...digests];
|
|
73
|
+
}), maintain = (signal) => Effect.gen(function* () {
|
|
74
|
+
const all = yield* Ref.get(entries), decision = yield* policy.maintain({
|
|
75
|
+
entries: all,
|
|
76
|
+
signal,
|
|
77
|
+
turn: currentTurnOf(all),
|
|
78
|
+
runId,
|
|
79
|
+
render: (candidate) => renderLog(candidate, renderOptions(candidate, "tail"))
|
|
80
|
+
}), compactions = yield* record(decision.actions.map((action) => ({ _tag: "Compaction", strategy: policy.strategy.id, version: policy.strategy.version, action })), 0), wanted = new Set(decision.digest.map(String)), digests = yield* digest(all.filter((entry) => wanted.has(String(entry.id))), signal.views, "compaction", 0);
|
|
81
|
+
return [...compactions, ...digests];
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
strategy: policy.strategy,
|
|
85
|
+
renderRecipe: (stepContext) => Ref.get(entries).pipe(Effect.map((all) => renderOptions(all, stepContext))),
|
|
86
|
+
turn: Ref.get(entries).pipe(Effect.map(currentTurnOf)),
|
|
87
|
+
entries: Ref.get(entries),
|
|
88
|
+
query: (query) => Ref.get(entries).pipe(Effect.map((all) => queryLog(all, query))),
|
|
89
|
+
subjects: (kinds) => Ref.get(entries).pipe(Effect.map((all) => subjectsOf(all, kinds))),
|
|
90
|
+
resolve: (id) => Ref.get(entries).pipe(Effect.map((all) => Option.fromNullishOr(all.find((entry) => entry.id === id)))),
|
|
91
|
+
transcript: (fidelity) => Ref.get(entries).pipe(Effect.map((all) => fidelity === "raw" ? rawTranscript(all) : fidelity === "reference" ? referenceTranscript(all) : renderLog(all, renderOptions(all, "tail")))),
|
|
92
|
+
record,
|
|
93
|
+
recordTail,
|
|
94
|
+
maintain,
|
|
95
|
+
build: ({ stepContext }) => Ref.get(entries).pipe(Effect.map((all) => buildContext(all, renderOptions(all, stepContext))))
|
|
96
|
+
};
|
|
97
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context, Effect, Option, Ref } from "effect";
|
|
2
|
+
import type { ConformanceCheck } from "../conformance.entity.js";
|
|
3
|
+
import type { ConversationMemory, LogHandle } from "../ports/memory.port.js";
|
|
4
|
+
/**
|
|
5
|
+
* A memory log held as session events, through the storage codec: what a
|
|
6
|
+
* turn's log does, without a session. The user's message is stored as its
|
|
7
|
+
* own `turn.started`, as a session's begin stores it.
|
|
8
|
+
*/
|
|
9
|
+
export declare const inMemoryLog: Effect.Effect<{
|
|
10
|
+
stored: Ref.Ref<readonly {
|
|
11
|
+
readonly session: string & import("effect/Brand").Brand<"ConversationId">;
|
|
12
|
+
readonly seq: number;
|
|
13
|
+
readonly turn: Option.Option<number>;
|
|
14
|
+
readonly kind: string;
|
|
15
|
+
readonly at: number;
|
|
16
|
+
readonly data: {
|
|
17
|
+
readonly [x: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
}[]>;
|
|
20
|
+
log: LogHandle;
|
|
21
|
+
}, never, never>;
|
|
22
|
+
type Memory = Context.Service.Shape<typeof ConversationMemory>;
|
|
23
|
+
/**
|
|
24
|
+
* The ConversationMemory contract, as checks any strategy must pass: own
|
|
25
|
+
* entry ids, a log that reads back exactly after a reopen, views and
|
|
26
|
+
* artifacts kept, a pure build, the step context only when asked,
|
|
27
|
+
* append-only maintenance and a reference transcript without tool traffic.
|
|
28
|
+
* `services` are the turn services the strategy needs (a summarizer…); each
|
|
29
|
+
* session is opened with them provided.
|
|
30
|
+
*/
|
|
31
|
+
export declare const memoryConformance: (memory: Memory, services?: Context.Context<never>) => ReadonlyArray<ConformanceCheck>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Context, Effect, Option, Ref, Schema } from "effect";
|
|
2
|
+
import { ConformanceFailure } from "../conformance.entity.js";
|
|
3
|
+
import { ConversationId, ToolCallId } from "../domain/message.entity.js";
|
|
4
|
+
import { HarnessError } from "../harness/plugin.entity.js";
|
|
5
|
+
import { draftOfEntry, entriesOfEvents, turnStartedDraft } from "../session/session-event.entity.functions.js";
|
|
6
|
+
import { UserMessage } from "../turn/user-message.entity.js";
|
|
7
|
+
import { canonicalJson } from "./memory-log.entity.functions.js";
|
|
8
|
+
import { LogEntry } from "./memory-log.entity.js";
|
|
9
|
+
const conversation = ConversationId.make("00000000-0000-4000-8000-00000000c0f0");
|
|
10
|
+
export const inMemoryLog = Effect.gen(function* () {
|
|
11
|
+
const stored = yield* Ref.make([]), codec = (error) => new HarnessError({ code: "memory.log", message: error.message }), log = {
|
|
12
|
+
read: Ref.get(stored).pipe(Effect.flatMap(entriesOfEvents), Effect.mapError(codec)),
|
|
13
|
+
append: (entries) => Effect.forEach(entries, (entry) => (entry.body._tag === "TurnStarted" ? turnStartedDraft(entry.turn, {
|
|
14
|
+
runId: entry.runId,
|
|
15
|
+
key: entry.runId,
|
|
16
|
+
origin: "user",
|
|
17
|
+
userMessage: entry.body.userMessage,
|
|
18
|
+
command: {},
|
|
19
|
+
claimed: [],
|
|
20
|
+
entry: entry.id,
|
|
21
|
+
at: entry.at
|
|
22
|
+
}).pipe(Effect.map(Option.some)) : draftOfEntry(entry)).pipe(Effect.mapError(codec))).pipe(Effect.flatMap((drafts) => Ref.update(stored, (all) => [...all, ...drafts.flatMap(Option.toArray).map((draft, index) => ({
|
|
23
|
+
session: conversation,
|
|
24
|
+
seq: all.length + index + 1,
|
|
25
|
+
turn: draft.turn,
|
|
26
|
+
kind: draft.kind,
|
|
27
|
+
at: 0,
|
|
28
|
+
data: JSON.parse(JSON.stringify(draft.data))
|
|
29
|
+
}))])))
|
|
30
|
+
};
|
|
31
|
+
return { stored, log };
|
|
32
|
+
});
|
|
33
|
+
const artifact = { id: "artifact-1", kind: "image", mediaType: "image/png", url: "https://example.test/artifact-1.png", alt: Option.some("a figure") }, views = {
|
|
34
|
+
view: (_tool, encoded) => Effect.succeed({ text: `VIEW ${canonicalJson(encoded)}`, version: "1", subjects: [], artifacts: [artifact], pinned: !1 }),
|
|
35
|
+
compact: () => Effect.succeed(Option.none()),
|
|
36
|
+
digest: () => Effect.succeed(Option.none())
|
|
37
|
+
}, callId = ToolCallId.make("conformance-call"), toolTail = [
|
|
38
|
+
{ role: "assistant", content: [{ type: "tool-call", toolCallId: callId, toolName: "lookup", input: { query: "alpha" } }] },
|
|
39
|
+
{ role: "tool", content: [{ type: "tool-result", toolCallId: callId, toolName: "lookup", output: { id: "record-alpha" }, isError: !1 }] }
|
|
40
|
+
], encodeEntries = (entries) => canonicalJson(Schema.encodeSync(Schema.Array(LogEntry))(entries)), fail = (check) => (message) => Effect.fail(new ConformanceFailure({ check, message })), expect = (check, holds, message) => holds ? Effect.void : fail(check)(message), said = (text) => ({ _tag: "TurnStarted", userMessage: new UserMessage({ text }) }), recordTurn = (session, userMessage, reply) => Effect.gen(function* () {
|
|
41
|
+
yield* session.record([said(userMessage)], 0);
|
|
42
|
+
yield* session.recordTail(toolTail, views, 0);
|
|
43
|
+
yield* session.record([{ _tag: "TurnEnded", outcome: "completed", reply: Option.some(reply) }], 1);
|
|
44
|
+
});
|
|
45
|
+
export const memoryConformance = (memory, services = Context.empty()) => {
|
|
46
|
+
const check = (name, id, body) => ({
|
|
47
|
+
name,
|
|
48
|
+
run: Effect.scoped(Effect.gen(function* () {
|
|
49
|
+
const stored = yield* inMemoryLog;
|
|
50
|
+
yield* body((log, runId) => memory.open({ conversation, runId, log }).pipe(Effect.provide(services)), stored.log);
|
|
51
|
+
})).pipe(Effect.catchTag("HarnessError", (error) => fail(id)(`${error.code}: ${error.message}`)))
|
|
52
|
+
});
|
|
53
|
+
return [
|
|
54
|
+
check("entries get their own ids, <runId>:<n>, continuing across reopens", "ids", (open, log) => Effect.gen(function* () {
|
|
55
|
+
const a = yield* (yield* open(log, "run-1")).record([said("one"), { _tag: "TurnContext", sectionId: "s", version: "1", text: "context" }], 0), b = yield* (yield* open(log, "run-1")).record([{ _tag: "TurnEnded", outcome: "completed", reply: Option.none() }], 0), c = yield* (yield* open(log, "run-2")).record([said("two")], 0), ids = [...a, ...b, ...c].map((entry) => String(entry.id));
|
|
56
|
+
yield* expect("ids", ids.join() === "run-1:0,run-1:1,run-1:2,run-2:0", `ids were ${ids.join()}`);
|
|
57
|
+
})),
|
|
58
|
+
check("a reopened session reads back exactly what was recorded, and builds the same context", "reopen", (open, log) => Effect.gen(function* () {
|
|
59
|
+
const session = yield* open(log, "run-1");
|
|
60
|
+
yield* recordTurn(session, "find alpha", "found record-alpha");
|
|
61
|
+
const reopened = yield* open(log, "run-1");
|
|
62
|
+
yield* expect("reopen", encodeEntries(yield* reopened.entries) === encodeEntries(yield* session.entries), "the reopened log differs");
|
|
63
|
+
const [left, right] = [yield* session.build({ stepContext: "tail" }), yield* reopened.build({ stepContext: "tail" })];
|
|
64
|
+
yield* expect("reopen", left.fingerprint === right.fingerprint, "the reopened session builds a different context");
|
|
65
|
+
})),
|
|
66
|
+
check("tool results keep their views and artifacts", "views", (open, log) => Effect.gen(function* () {
|
|
67
|
+
const session = yield* open(log, "run-1");
|
|
68
|
+
yield* recordTurn(session, "find alpha", "found record-alpha");
|
|
69
|
+
const result = (yield* (yield* open(log, "run-1")).entries).find((entry) => entry.body._tag === "ToolResult");
|
|
70
|
+
yield* expect("views", result?.body._tag === "ToolResult" && result.body.view.startsWith("VIEW ") && result.body.artifacts[0]?.id === artifact.id, "the tool result lost its view or artifacts");
|
|
71
|
+
})),
|
|
72
|
+
check("build is a pure fold of the log", "pure", (open, log) => Effect.gen(function* () {
|
|
73
|
+
const session = yield* open(log, "run-1");
|
|
74
|
+
yield* recordTurn(session, "find alpha", "found record-alpha");
|
|
75
|
+
const [left, right] = [yield* session.build({ stepContext: "tail" }), yield* session.build({ stepContext: "tail" })];
|
|
76
|
+
yield* expect("pure", left.fingerprint === right.fingerprint && canonicalJson(left.messages) === canonicalJson(right.messages), "two builds differ");
|
|
77
|
+
})),
|
|
78
|
+
check("the step context closes the tail only when asked", "step-context", (open, log) => Effect.gen(function* () {
|
|
79
|
+
const session = yield* open(log, "run-1");
|
|
80
|
+
yield* session.record([said("find alpha")], 0);
|
|
81
|
+
yield* session.record([{ _tag: "StepContext", step: 0, text: "STEP-MARK" }], 0);
|
|
82
|
+
const tail = canonicalJson((yield* session.build({ stepContext: "tail" })).messages), none = canonicalJson((yield* session.build({ stepContext: "none" })).messages);
|
|
83
|
+
yield* expect("step-context", tail.includes("STEP-MARK") && !none.includes("STEP-MARK"), "the step context is misplaced");
|
|
84
|
+
})),
|
|
85
|
+
check("maintenance only appends to the log", "append-only", (open, log) => Effect.gen(function* () {
|
|
86
|
+
const session = yield* open(log, "run-1");
|
|
87
|
+
yield* recordTurn(session, "find alpha", "found record-alpha");
|
|
88
|
+
yield* session.record([said("and beta")], 0);
|
|
89
|
+
const before = yield* session.entries;
|
|
90
|
+
yield* session.maintain({ phase: "turn-start", lastUsage: Option.none(), budgetTokens: 1e5, views });
|
|
91
|
+
yield* session.maintain({ phase: "step", lastUsage: Option.none(), budgetTokens: 1e5, views });
|
|
92
|
+
const after = yield* session.entries;
|
|
93
|
+
yield* expect("append-only", encodeEntries(after.slice(0, before.length)) === encodeEntries(before), "maintenance rewrote earlier entries");
|
|
94
|
+
})),
|
|
95
|
+
check("the reference transcript holds user messages and replies, never tool traffic", "reference", (open, log) => Effect.gen(function* () {
|
|
96
|
+
const session = yield* open(log, "run-1");
|
|
97
|
+
yield* recordTurn(session, "find alpha", "found record-alpha");
|
|
98
|
+
const transcript = yield* session.transcript("reference"), text = canonicalJson(transcript);
|
|
99
|
+
yield* expect("reference", transcript.every((message) => message.role === "user" || message.role === "assistant") && text.includes("find alpha") && text.includes("found record-alpha") && !text.includes("tool-call"), `reference transcript: ${text.slice(0, 300)}`);
|
|
100
|
+
}))
|
|
101
|
+
];
|
|
102
|
+
};
|
|
@@ -7,30 +7,28 @@ import type { ProviderId } from "../domain/model-selection.entity.js";
|
|
|
7
7
|
* needs no key. Matches the on-disk `auth.json` vocabulary the previous line
|
|
8
8
|
* established, so existing credentials keep working.
|
|
9
9
|
*/
|
|
10
|
-
export declare const Credential: Schema.Union<[Schema.Struct<{
|
|
11
|
-
type: Schema.Literal<
|
|
12
|
-
key:
|
|
10
|
+
export declare const Credential: Schema.Union<readonly [Schema.Struct<{
|
|
11
|
+
readonly type: Schema.Literal<"api_key">;
|
|
12
|
+
readonly key: Schema.String;
|
|
13
13
|
}>, Schema.Struct<{
|
|
14
|
-
type: Schema.Literal<
|
|
15
|
-
access:
|
|
16
|
-
refresh:
|
|
17
|
-
expires:
|
|
18
|
-
accountId: Schema.optional<
|
|
19
|
-
installationId: Schema.optional<
|
|
14
|
+
readonly type: Schema.Literal<"oauth">;
|
|
15
|
+
readonly access: Schema.String;
|
|
16
|
+
readonly refresh: Schema.String;
|
|
17
|
+
readonly expires: Schema.Number;
|
|
18
|
+
readonly accountId: Schema.optional<Schema.String>;
|
|
19
|
+
readonly installationId: Schema.optional<Schema.String>;
|
|
20
20
|
}>, Schema.Struct<{
|
|
21
|
-
type: Schema.Literal<
|
|
22
|
-
baseUrl: Schema.optional<
|
|
21
|
+
readonly type: Schema.Literal<"local">;
|
|
22
|
+
readonly baseUrl: Schema.optional<Schema.String>;
|
|
23
23
|
}>]>;
|
|
24
24
|
export type Credential = typeof Credential.Type;
|
|
25
|
-
declare const AuthError_base: Schema.
|
|
26
|
-
readonly
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
message: typeof Schema.String;
|
|
30
|
-
}>;
|
|
25
|
+
declare const AuthError_base: Schema.Class<AuthError, Schema.TaggedStruct<"AuthError", {
|
|
26
|
+
readonly provider: Schema.String;
|
|
27
|
+
readonly message: Schema.String;
|
|
28
|
+
}>, import("effect/Cause").YieldableError>;
|
|
31
29
|
export declare class AuthError extends AuthError_base {
|
|
32
30
|
}
|
|
33
|
-
declare const AuthStore_base: Context.
|
|
31
|
+
declare const AuthStore_base: Context.ServiceClass<AuthStore, "@xandreed/core/AuthStore", {
|
|
34
32
|
readonly all: Effect.Effect<ReadonlyMap<string, Credential>, AuthError>;
|
|
35
33
|
readonly get: (p: ProviderId) => Effect.Effect<Option.Option<Credential>, AuthError>;
|
|
36
34
|
readonly resolveKey: (p: ProviderId) => Effect.Effect<Option.Option<Redacted.Redacted<string>>, AuthError>;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { Context, Option, Schema } from "effect";
|
|
2
|
-
export const Credential = Schema.Union(
|
|
3
|
-
type: Schema.Literal("
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
export const Credential = Schema.Union([
|
|
3
|
+
Schema.Struct({ type: Schema.Literal("api_key"), key: Schema.String }),
|
|
4
|
+
Schema.Struct({
|
|
5
|
+
type: Schema.Literal("oauth"),
|
|
6
|
+
access: Schema.String,
|
|
7
|
+
refresh: Schema.String,
|
|
8
|
+
expires: Schema.Number,
|
|
9
|
+
accountId: Schema.optional(Schema.String),
|
|
10
|
+
installationId: Schema.optional(Schema.String)
|
|
11
|
+
}),
|
|
12
|
+
Schema.Struct({ type: Schema.Literal("local"), baseUrl: Schema.optional(Schema.String) })
|
|
13
|
+
]);
|
|
10
14
|
|
|
11
15
|
export class AuthError extends Schema.TaggedError()("AuthError", {
|
|
12
16
|
provider: Schema.String,
|
|
@@ -14,5 +18,5 @@ export class AuthError extends Schema.TaggedError()("AuthError", {
|
|
|
14
18
|
}) {
|
|
15
19
|
}
|
|
16
20
|
|
|
17
|
-
export class AuthStore extends Context.
|
|
21
|
+
export class AuthStore extends Context.Service()("@xandreed/core/AuthStore") {
|
|
18
22
|
}
|
|
@@ -1,15 +1,139 @@
|
|
|
1
1
|
import { Context } from "effect";
|
|
2
|
-
import type { Effect } from "effect";
|
|
3
|
-
import type {
|
|
2
|
+
import type { Effect, Option } from "effect";
|
|
3
|
+
import type { LanguageModel, Tool } from "effect/ai";
|
|
4
|
+
import type { AgentMessage } from "../domain/message.entity.js";
|
|
5
|
+
import type { TokenUsage } from "../domain/token-usage.entity.js";
|
|
6
|
+
import type { PromptTier, SkillDefinition, ToolAnnotations } from "../harness/capability.entity.js";
|
|
4
7
|
import type { HarnessError } from "../harness/plugin.entity.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
import type { ArtifactRef, Subject } from "../memory/memory-log.entity.js";
|
|
9
|
+
import type { UserMessage } from "../turn/user-message.entity.js";
|
|
10
|
+
/**
|
|
11
|
+
* How a tool's long result may be digested for the current user message, with the
|
|
12
|
+
* tool's own prompt. The memory strategy decides WHEN (on write above a size,
|
|
13
|
+
* or at compaction); the digester runs it; the outcome is logged once.
|
|
14
|
+
* `Select` (preferred) keeps whole items by key and re-renders them, so every
|
|
15
|
+
* identifier the answer may cite survives. `Summarize` is accepted only when
|
|
16
|
+
* every `preserve`d identifier appears verbatim in the summary.
|
|
17
|
+
*/
|
|
18
|
+
export type DigestDefinition<Result, Params> = {
|
|
19
|
+
readonly _tag: "Select";
|
|
20
|
+
readonly version: string;
|
|
21
|
+
readonly instructions: string;
|
|
22
|
+
readonly items: (result: Result) => ReadonlyArray<{
|
|
23
|
+
readonly key: string;
|
|
24
|
+
readonly text: string;
|
|
25
|
+
}>;
|
|
26
|
+
readonly render: (result: Result, params: Params, keep: ReadonlyArray<string>) => string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly _tag: "Summarize";
|
|
29
|
+
readonly version: string;
|
|
30
|
+
readonly instructions: string;
|
|
31
|
+
readonly preserve: (result: Result) => ReadonlyArray<string>;
|
|
32
|
+
};
|
|
33
|
+
/** How one tool's result appears in context — owned by the tool, applied by memory. */
|
|
34
|
+
export interface ToolViewDefinition<Result, Params> {
|
|
35
|
+
readonly version: string;
|
|
36
|
+
/** The exact text the model sees when the result is written. */
|
|
37
|
+
readonly render: (result: Result, params: Params) => string;
|
|
38
|
+
/** The older-turn form; None keeps `render`'s text. */
|
|
39
|
+
readonly compact: Option.Option<(result: Result, params: Params) => string>;
|
|
40
|
+
readonly subjects: (result: Result, params: Params) => ReadonlyArray<Subject>;
|
|
41
|
+
/** Files and images the result carries, kept by reference. */
|
|
42
|
+
readonly artifacts: (result: Result, params: Params) => ReadonlyArray<ArtifactRef>;
|
|
43
|
+
readonly digest: Option.Option<DigestDefinition<Result, Params>>;
|
|
44
|
+
}
|
|
45
|
+
/** The view as an author writes it; optional parts default in `defineTool`. */
|
|
46
|
+
export interface ToolViewInput<Result, Params> {
|
|
47
|
+
readonly version: string;
|
|
48
|
+
readonly render: (result: Result, params: Params) => string;
|
|
49
|
+
readonly compact?: (result: Result, params: Params) => string;
|
|
50
|
+
readonly subjects?: (result: Result, params: Params) => ReadonlyArray<Subject>;
|
|
51
|
+
readonly artifacts?: (result: Result, params: Params) => ReadonlyArray<ArtifactRef>;
|
|
52
|
+
readonly digest?: DigestDefinition<Result, Params>;
|
|
53
|
+
}
|
|
54
|
+
/** A typed tool definition as its author writes it (see `defineTool`). */
|
|
55
|
+
export interface ToolDefinition<T extends Tool.Any, R> {
|
|
56
|
+
readonly tool: T;
|
|
57
|
+
readonly handler: (params: Tool.Parameters<T>) => Effect.Effect<Tool.Success<T>, Tool.Failure<T>, R>;
|
|
58
|
+
readonly view: Option.Option<ToolViewDefinition<Tool.Success<T>, Tool.Parameters<T>>>;
|
|
59
|
+
readonly annotations: ToolAnnotations;
|
|
60
|
+
}
|
|
61
|
+
/** The erased form the registry consumes; its requirements are provided per run. */
|
|
62
|
+
export interface RegisteredTool {
|
|
63
|
+
readonly tool: Tool.Any;
|
|
64
|
+
readonly handler: (params: unknown) => Effect.Effect<unknown, unknown, unknown>;
|
|
65
|
+
readonly view: Option.Option<ToolViewDefinition<unknown, unknown>>;
|
|
66
|
+
readonly annotations: ToolAnnotations;
|
|
67
|
+
}
|
|
68
|
+
export interface PromptContext {
|
|
69
|
+
readonly variant: Option.Option<string>;
|
|
70
|
+
readonly active: ReadonlyArray<string>;
|
|
71
|
+
readonly skills: ReadonlyArray<SkillDefinition>;
|
|
72
|
+
}
|
|
73
|
+
/** One section of the system prompt (static/session) or of the turn context (turn). */
|
|
74
|
+
export interface PromptSection {
|
|
75
|
+
readonly id: string;
|
|
76
|
+
readonly version: string;
|
|
77
|
+
readonly tier: PromptTier;
|
|
78
|
+
readonly order: number;
|
|
79
|
+
readonly render: (context: PromptContext) => Effect.Effect<Option.Option<string>, HarnessError, unknown>;
|
|
80
|
+
}
|
|
81
|
+
export interface StepInfo {
|
|
82
|
+
readonly stepIndex: number;
|
|
83
|
+
readonly activeTools: ReadonlyArray<string>;
|
|
84
|
+
readonly lastUsage: Option.Option<TokenUsage>;
|
|
85
|
+
}
|
|
86
|
+
export type ToolChoice = "required" | {
|
|
87
|
+
readonly tool: string;
|
|
88
|
+
};
|
|
89
|
+
export interface StepDirective {
|
|
90
|
+
/** Appended at the end of the context for this step only. */
|
|
91
|
+
readonly context: Option.Option<string>;
|
|
92
|
+
readonly toolChoice: Option.Option<ToolChoice>;
|
|
93
|
+
}
|
|
94
|
+
export interface InitialBatch {
|
|
95
|
+
readonly calls: ReadonlyArray<{
|
|
96
|
+
readonly name: string;
|
|
97
|
+
readonly params: unknown;
|
|
98
|
+
}>;
|
|
99
|
+
/** Skills the batch needs, activated before it runs. */
|
|
100
|
+
readonly skills: ReadonlyArray<string>;
|
|
101
|
+
}
|
|
102
|
+
export interface ModelChoice {
|
|
103
|
+
readonly model: LanguageModel.LanguageModel;
|
|
104
|
+
/** Selects a system-prompt variant; sections receive it. */
|
|
105
|
+
readonly variant: Option.Option<string>;
|
|
106
|
+
}
|
|
107
|
+
/** One bundle a host (or a capability plugin) contributes. */
|
|
108
|
+
export interface Capability {
|
|
109
|
+
readonly id: string;
|
|
110
|
+
readonly version: string;
|
|
111
|
+
readonly tools: ReadonlyArray<RegisteredTool>;
|
|
112
|
+
readonly skills: ReadonlyArray<SkillDefinition>;
|
|
113
|
+
readonly promptSections: ReadonlyArray<PromptSection>;
|
|
114
|
+
}
|
|
115
|
+
declare const Capabilities_base: Context.ServiceClass<Capabilities, "efferent/Capabilities", readonly Capability[]>;
|
|
116
|
+
/** A MULTI-provider key: every contributor's bundle, in graph order. */
|
|
8
117
|
export declare class Capabilities extends Capabilities_base {
|
|
9
118
|
}
|
|
10
|
-
|
|
11
|
-
readonly
|
|
119
|
+
export interface IntentMatch {
|
|
120
|
+
readonly skills: ReadonlyArray<string>;
|
|
121
|
+
readonly probabilities: Option.Option<Readonly<Record<string, number>>>;
|
|
122
|
+
/** The matcher declined to choose; the turn runs on always-on skills. */
|
|
123
|
+
readonly abstained: boolean;
|
|
124
|
+
}
|
|
125
|
+
declare const IntentMatcher_base: Context.ServiceClass<IntentMatcher, "efferent/IntentMatcher", {
|
|
126
|
+
readonly id: string;
|
|
127
|
+
readonly version: string;
|
|
128
|
+
readonly match: (input: {
|
|
129
|
+
readonly userMessage: UserMessage;
|
|
130
|
+
readonly skills: ReadonlyArray<SkillDefinition>;
|
|
131
|
+
readonly active: ReadonlyArray<string>;
|
|
132
|
+
/** A reference transcript: user messages and replies only. */
|
|
133
|
+
readonly history: ReadonlyArray<AgentMessage>;
|
|
134
|
+
}) => Effect.Effect<IntentMatch, HarnessError>;
|
|
12
135
|
}>;
|
|
136
|
+
/** Pre-turn skill selection. Probabilistic intent never authorizes: the registry resolves grants. */
|
|
13
137
|
export declare class IntentMatcher extends IntentMatcher_base {
|
|
14
138
|
}
|
|
15
139
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Context } from "effect";
|
|
2
2
|
|
|
3
|
-
export class Capabilities extends Context.
|
|
3
|
+
export class Capabilities extends Context.Service()("efferent/Capabilities") {
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
export class IntentMatcher extends Context.
|
|
6
|
+
export class IntentMatcher extends Context.Service()("efferent/IntentMatcher") {
|
|
7
7
|
}
|