world-model-optimizer 0.2.0__py3-none-any.whl
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.
- llm_waterfall/LICENSE +21 -0
- llm_waterfall/__init__.py +53 -0
- llm_waterfall/adapters/__init__.py +36 -0
- llm_waterfall/adapters/anthropic.py +105 -0
- llm_waterfall/adapters/aws_mantle.py +47 -0
- llm_waterfall/adapters/azure_openai.py +71 -0
- llm_waterfall/adapters/base.py +51 -0
- llm_waterfall/adapters/bedrock.py +309 -0
- llm_waterfall/adapters/openai.py +130 -0
- llm_waterfall/classify.py +184 -0
- llm_waterfall/pricing.py +110 -0
- llm_waterfall/py.typed +0 -0
- llm_waterfall/types.py +295 -0
- llm_waterfall/waterfall.py +255 -0
- wmo/__init__.py +38 -0
- wmo/agents/__init__.py +7 -0
- wmo/agents/default.py +29 -0
- wmo/agents/meta.py +55 -0
- wmo/agents/optimizer.py +55 -0
- wmo/agents/project.py +928 -0
- wmo/cli/__init__.py +5 -0
- wmo/cli/agent_session.py +1123 -0
- wmo/cli/app.py +2489 -0
- wmo/cli/e2b_cmds.py +212 -0
- wmo/cli/eval_closed_loop.py +207 -0
- wmo/cli/harness_app.py +1147 -0
- wmo/cli/harness_distill.py +659 -0
- wmo/cli/hosted_session.py +880 -0
- wmo/cli/ingest_cmd.py +165 -0
- wmo/cli/model_roles.py +82 -0
- wmo/cli/platform_cmds.py +372 -0
- wmo/cli/route_app.py +274 -0
- wmo/cli/session_state.py +243 -0
- wmo/cli/ui.py +1107 -0
- wmo/cli/workspace_sync.py +504 -0
- wmo/config/__init__.py +60 -0
- wmo/config/card.py +129 -0
- wmo/config/config.py +367 -0
- wmo/config/dotenv.py +67 -0
- wmo/config/settings.py +128 -0
- wmo/config/store.py +177 -0
- wmo/conftest.py +19 -0
- wmo/connect/__init__.py +88 -0
- wmo/connect/apps.py +78 -0
- wmo/connect/brave.py +284 -0
- wmo/connect/connector.py +79 -0
- wmo/connect/credentials.py +164 -0
- wmo/connect/github.py +321 -0
- wmo/connect/google.py +627 -0
- wmo/connect/notion.py +790 -0
- wmo/connect/oauth.py +461 -0
- wmo/connect/slack.py +555 -0
- wmo/connect/store.py +199 -0
- wmo/connect/types.py +156 -0
- wmo/core/__init__.py +21 -0
- wmo/core/parsing.py +281 -0
- wmo/core/render.py +271 -0
- wmo/core/text.py +40 -0
- wmo/core/types.py +116 -0
- wmo/distill/__init__.py +14 -0
- wmo/distill/agents.py +140 -0
- wmo/distill/config.py +1006 -0
- wmo/distill/cost.py +437 -0
- wmo/distill/data.py +921 -0
- wmo/distill/deadlines.py +254 -0
- wmo/distill/fake_tinker.py +734 -0
- wmo/distill/gate.py +122 -0
- wmo/distill/loop.py +3499 -0
- wmo/distill/renderers.py +399 -0
- wmo/distill/rendering.py +620 -0
- wmo/distill/rollouts.py +726 -0
- wmo/distill/samples.py +195 -0
- wmo/distill/store.py +829 -0
- wmo/distill/teacher.py +714 -0
- wmo/distill/tokens.py +535 -0
- wmo/distill/tracking.py +552 -0
- wmo/distill/tripwire.py +411 -0
- wmo/distill/xtoken/byte_offsets.py +152 -0
- wmo/distill/xtoken/chunks.py +457 -0
- wmo/distill/xtoken/prompt_logprobs.py +475 -0
- wmo/distill/xtoken/teacher_render.py +346 -0
- wmo/engine/__init__.py +28 -0
- wmo/engine/autoconfig.py +367 -0
- wmo/engine/build.py +346 -0
- wmo/engine/demo.py +77 -0
- wmo/engine/eval_suites.py +245 -0
- wmo/engine/grounding.py +491 -0
- wmo/engine/knowledge.py +291 -0
- wmo/engine/loader.py +36 -0
- wmo/engine/play.py +92 -0
- wmo/engine/prompts.py +99 -0
- wmo/engine/replay.py +443 -0
- wmo/engine/reporting.py +58 -0
- wmo/engine/workspace.py +468 -0
- wmo/engine/world_model.py +568 -0
- wmo/env/__init__.py +22 -0
- wmo/env/base.py +121 -0
- wmo/env/closed_loop.py +229 -0
- wmo/env/episode.py +107 -0
- wmo/env/llm_agent.py +93 -0
- wmo/env/scenarios.py +73 -0
- wmo/evals/__init__.py +52 -0
- wmo/evals/agreement.py +110 -0
- wmo/evals/base.py +45 -0
- wmo/evals/closed_loop.py +480 -0
- wmo/evals/failover.py +96 -0
- wmo/evals/gold.py +127 -0
- wmo/evals/grid.py +394 -0
- wmo/evals/grid_plot.py +205 -0
- wmo/evals/harbor/__init__.py +27 -0
- wmo/evals/harbor/agent.py +573 -0
- wmo/evals/harbor/ctrf.py +171 -0
- wmo/evals/harbor/e2b_environment.py +587 -0
- wmo/evals/harbor/e2b_template_policy.py +144 -0
- wmo/evals/harbor/scorer.py +875 -0
- wmo/evals/harbor/tasks.py +140 -0
- wmo/evals/open_loop.py +194 -0
- wmo/evals/tasks.py +53 -0
- wmo/harness/__init__.py +51 -0
- wmo/harness/code_runtime.py +288 -0
- wmo/harness/create.py +1191 -0
- wmo/harness/delta.py +220 -0
- wmo/harness/doc.py +556 -0
- wmo/harness/e2b_ledger.py +342 -0
- wmo/harness/e2b_reap.py +476 -0
- wmo/harness/e2b_sandbox.py +350 -0
- wmo/harness/environment.py +35 -0
- wmo/harness/live_session.py +543 -0
- wmo/harness/mutate.py +343 -0
- wmo/harness/pi_e2b.py +1710 -0
- wmo/harness/pi_entry/entry.ts +268 -0
- wmo/harness/pi_entry/runner_frames.ts +92 -0
- wmo/harness/pi_entry/runner_live.ts +587 -0
- wmo/harness/pi_entry/runner_service.ts +270 -0
- wmo/harness/pi_entry/runner_stdio.ts +374 -0
- wmo/harness/pi_entry/runner_termination.ts +142 -0
- wmo/harness/pi_local.py +262 -0
- wmo/harness/pi_runtime.py +495 -0
- wmo/harness/pi_vendor.py +65 -0
- wmo/harness/population.py +509 -0
- wmo/harness/project_proposer.py +569 -0
- wmo/harness/proposer.py +977 -0
- wmo/harness/runner_link.py +619 -0
- wmo/harness/runtime.py +389 -0
- wmo/harness/scoring.py +247 -0
- wmo/harness/skills.py +116 -0
- wmo/harness/source_tree.py +319 -0
- wmo/harness/store.py +176 -0
- wmo/harness/tools.py +105 -0
- wmo/harness/vendor/manifest.sha256 +58 -0
- wmo/harness/vendor/pi-agent/CHANGELOG.md +556 -0
- wmo/harness/vendor/pi-agent/LICENSE +21 -0
- wmo/harness/vendor/pi-agent/README.md +488 -0
- wmo/harness/vendor/pi-agent/VENDOR.md +39 -0
- wmo/harness/vendor/pi-agent/docs/agent-harness.md +486 -0
- wmo/harness/vendor/pi-agent/docs/durable-harness.md +212 -0
- wmo/harness/vendor/pi-agent/docs/hooks.md +445 -0
- wmo/harness/vendor/pi-agent/docs/models.md +966 -0
- wmo/harness/vendor/pi-agent/docs/observability.md +376 -0
- wmo/harness/vendor/pi-agent/package.json +60 -0
- wmo/harness/vendor/pi-agent/src/agent-loop.ts +748 -0
- wmo/harness/vendor/pi-agent/src/agent.ts +575 -0
- wmo/harness/vendor/pi-agent/src/harness/agent-harness.ts +1029 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/branch-summarization.ts +261 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/compaction.ts +747 -0
- wmo/harness/vendor/pi-agent/src/harness/compaction/utils.ts +144 -0
- wmo/harness/vendor/pi-agent/src/harness/env/nodejs.ts +550 -0
- wmo/harness/vendor/pi-agent/src/harness/messages.ts +164 -0
- wmo/harness/vendor/pi-agent/src/harness/prompt-templates.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-repo.ts +177 -0
- wmo/harness/vendor/pi-agent/src/harness/session/jsonl-storage.ts +293 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-repo.ts +50 -0
- wmo/harness/vendor/pi-agent/src/harness/session/memory-storage.ts +131 -0
- wmo/harness/vendor/pi-agent/src/harness/session/repo-utils.ts +51 -0
- wmo/harness/vendor/pi-agent/src/harness/session/session.ts +267 -0
- wmo/harness/vendor/pi-agent/src/harness/session/uuid.ts +54 -0
- wmo/harness/vendor/pi-agent/src/harness/skills.ts +375 -0
- wmo/harness/vendor/pi-agent/src/harness/system-prompt.ts +34 -0
- wmo/harness/vendor/pi-agent/src/harness/types.ts +836 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/shell-output.ts +135 -0
- wmo/harness/vendor/pi-agent/src/harness/utils/truncate.ts +344 -0
- wmo/harness/vendor/pi-agent/src/index.ts +44 -0
- wmo/harness/vendor/pi-agent/src/node.ts +2 -0
- wmo/harness/vendor/pi-agent/src/proxy.ts +367 -0
- wmo/harness/vendor/pi-agent/src/types.ts +428 -0
- wmo/harness/vendor/pi-agent/test/agent-loop.test.ts +1351 -0
- wmo/harness/vendor/pi-agent/test/agent.test.ts +699 -0
- wmo/harness/vendor/pi-agent/test/e2e.test.ts +404 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness-stream.test.ts +213 -0
- wmo/harness/vendor/pi-agent/test/harness/agent-harness.test.ts +608 -0
- wmo/harness/vendor/pi-agent/test/harness/compaction.test.ts +655 -0
- wmo/harness/vendor/pi-agent/test/harness/nodejs-env.test.ts +321 -0
- wmo/harness/vendor/pi-agent/test/harness/prompt-templates.test.ts +90 -0
- wmo/harness/vendor/pi-agent/test/harness/repo.test.ts +68 -0
- wmo/harness/vendor/pi-agent/test/harness/resource-formatting.test.ts +24 -0
- wmo/harness/vendor/pi-agent/test/harness/session-test-utils.ts +55 -0
- wmo/harness/vendor/pi-agent/test/harness/session-uuid.test.ts +50 -0
- wmo/harness/vendor/pi-agent/test/harness/session.test.ts +156 -0
- wmo/harness/vendor/pi-agent/test/harness/skills.test.ts +116 -0
- wmo/harness/vendor/pi-agent/test/harness/storage.test.ts +299 -0
- wmo/harness/vendor/pi-agent/test/harness/system-prompt.test.ts +66 -0
- wmo/harness/vendor/pi-agent/test/harness/truncate.test.ts +169 -0
- wmo/harness/vendor/pi-agent/test/scratch/simple.ts +72 -0
- wmo/harness/vendor/pi-agent/test/utils/calculate.ts +32 -0
- wmo/harness/vendor/pi-agent/test/utils/get-current-time.ts +46 -0
- wmo/harness/vendor/pi-agent/tsconfig.build.json +13 -0
- wmo/harness/vendor/pi-agent/vitest.config.ts +19 -0
- wmo/harness/vendor/pi-agent/vitest.harness.config.ts +28 -0
- wmo/harness/vendor/vendor_pi.sh +59 -0
- wmo/harness/workspace_patch.py +270 -0
- wmo/ingest/__init__.py +47 -0
- wmo/ingest/adapter.py +72 -0
- wmo/ingest/base.py +114 -0
- wmo/ingest/braintrust.py +339 -0
- wmo/ingest/detect.py +126 -0
- wmo/ingest/langfuse.py +291 -0
- wmo/ingest/langsmith.py +444 -0
- wmo/ingest/mastra.py +330 -0
- wmo/ingest/messages.py +170 -0
- wmo/ingest/normalize.py +679 -0
- wmo/ingest/otel_genai.py +69 -0
- wmo/ingest/otel_writer.py +100 -0
- wmo/ingest/phoenix.py +150 -0
- wmo/ingest/postgres.py +246 -0
- wmo/ingest/posthog.py +320 -0
- wmo/ingest/quality.py +28 -0
- wmo/ingest/stream.py +209 -0
- wmo/ingest/testdata/sample_otlp.json +60 -0
- wmo/ingest/testdata/sample_spans.jsonl +3 -0
- wmo/optimize/__init__.py +25 -0
- wmo/optimize/base.py +143 -0
- wmo/optimize/gepa.py +806 -0
- wmo/optimize/judge.py +262 -0
- wmo/optimize/judge_quality.py +359 -0
- wmo/optimize/knn.py +468 -0
- wmo/optimize/numeric.py +152 -0
- wmo/optimize/outcomes.py +103 -0
- wmo/optimize/policy.py +669 -0
- wmo/optimize/report.py +231 -0
- wmo/optimize/reward.py +129 -0
- wmo/optimize/routing.py +373 -0
- wmo/platform/__init__.py +6 -0
- wmo/platform/auth.py +115 -0
- wmo/platform/client.py +551 -0
- wmo/platform/credentials.py +126 -0
- wmo/platform/transfer.py +158 -0
- wmo/providers/__init__.py +40 -0
- wmo/providers/_bedrock_chat.py +155 -0
- wmo/providers/_openai_common.py +182 -0
- wmo/providers/_responses_common.py +472 -0
- wmo/providers/anthropic.py +134 -0
- wmo/providers/azure_openai.py +296 -0
- wmo/providers/base.py +300 -0
- wmo/providers/bedrock.py +312 -0
- wmo/providers/models.py +205 -0
- wmo/providers/openai.py +143 -0
- wmo/providers/openai_responses.py +240 -0
- wmo/providers/pool.py +170 -0
- wmo/providers/registry.py +73 -0
- wmo/providers/retry.py +151 -0
- wmo/providers/tinker.py +936 -0
- wmo/providers/waterfall.py +336 -0
- wmo/research/__init__.py +81 -0
- wmo/research/ablation.py +133 -0
- wmo/research/concurrency_plot.py +523 -0
- wmo/research/concurrency_run.py +240 -0
- wmo/research/concurrency_scaling.py +270 -0
- wmo/research/gepa_scaling.py +274 -0
- wmo/research/pipeline.py +198 -0
- wmo/research/scaling_split.py +82 -0
- wmo/research/scenario_fidelity.py +198 -0
- wmo/research/scenario_recovery.py +92 -0
- wmo/research/seed_stability.py +90 -0
- wmo/research/trace_scaling.py +348 -0
- wmo/retrieval/__init__.py +6 -0
- wmo/retrieval/embedders.py +105 -0
- wmo/retrieval/leakfree.py +52 -0
- wmo/retrieval/retriever.py +173 -0
- wmo/scenarios/__init__.py +58 -0
- wmo/scenarios/builder.py +152 -0
- wmo/scenarios/mining/__init__.py +27 -0
- wmo/scenarios/mining/clustering.py +171 -0
- wmo/scenarios/mining/facets.py +226 -0
- wmo/scenarios/mining/selection.py +220 -0
- wmo/scenarios/synthesis/__init__.py +6 -0
- wmo/scenarios/synthesis/scenario_set.py +63 -0
- wmo/scenarios/synthesis/synthesizer.py +85 -0
- wmo/scenarios/verification/__init__.py +17 -0
- wmo/scenarios/verification/judge.py +97 -0
- wmo/scenarios/verification/verify.py +135 -0
- wmo/serving/__init__.py +5 -0
- wmo/serving/builds.py +451 -0
- wmo/serving/chat.py +878 -0
- wmo/serving/endpoint_config.py +64 -0
- wmo/serving/savings.py +250 -0
- wmo/serving/server.py +553 -0
- wmo/serving/traces_source.py +206 -0
- wmo/telemetry.py +213 -0
- wmo/tracking/__init__.py +36 -0
- wmo/tracking/clock.py +24 -0
- wmo/tracking/metered.py +125 -0
- wmo/tracking/pricing.py +99 -0
- wmo/tracking/store.py +31 -0
- wmo/tracking/tracker.py +149 -0
- world_model_optimizer-0.2.0.dist-info/METADATA +203 -0
- world_model_optimizer-0.2.0.dist-info/RECORD +308 -0
- world_model_optimizer-0.2.0.dist-info/WHEEL +4 -0
- world_model_optimizer-0.2.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
import type { AssistantMessage, ImageContent, Model, Models, UserMessage } from "@earendil-works/pi-ai";
|
|
2
|
+
import { runAgentLoop } from "../agent-loop.ts";
|
|
3
|
+
import type {
|
|
4
|
+
AgentContext,
|
|
5
|
+
AgentEvent,
|
|
6
|
+
AgentLoopConfig,
|
|
7
|
+
AgentMessage,
|
|
8
|
+
AgentTool,
|
|
9
|
+
QueueMode,
|
|
10
|
+
StreamFn,
|
|
11
|
+
ThinkingLevel,
|
|
12
|
+
} from "../types.ts";
|
|
13
|
+
import { collectEntriesForBranchSummary, generateBranchSummary } from "./compaction/branch-summarization.ts";
|
|
14
|
+
import { compact, DEFAULT_COMPACTION_SETTINGS, prepareCompaction } from "./compaction/compaction.ts";
|
|
15
|
+
import { convertToLlm } from "./messages.ts";
|
|
16
|
+
import { formatPromptTemplateInvocation } from "./prompt-templates.ts";
|
|
17
|
+
import { formatSkillInvocation } from "./skills.ts";
|
|
18
|
+
import type {
|
|
19
|
+
AbortResult,
|
|
20
|
+
AgentHarnessEvent,
|
|
21
|
+
AgentHarnessEventResultMap,
|
|
22
|
+
AgentHarnessOptions,
|
|
23
|
+
AgentHarnessOwnEvent,
|
|
24
|
+
AgentHarnessPhase,
|
|
25
|
+
AgentHarnessResources,
|
|
26
|
+
AgentHarnessStreamOptions,
|
|
27
|
+
AgentHarnessStreamOptionsPatch,
|
|
28
|
+
ExecutionEnv,
|
|
29
|
+
NavigateTreeResult,
|
|
30
|
+
PendingSessionWrite,
|
|
31
|
+
PromptTemplate,
|
|
32
|
+
Session,
|
|
33
|
+
Skill,
|
|
34
|
+
} from "./types.ts";
|
|
35
|
+
import { AgentHarnessError, BranchSummaryError, CompactionError, SessionError, toError } from "./types.ts";
|
|
36
|
+
|
|
37
|
+
function createUserMessage(text: string, images?: ImageContent[]): UserMessage {
|
|
38
|
+
const content: Array<{ type: "text"; text: string } | ImageContent> = [{ type: "text", text }];
|
|
39
|
+
if (images) content.push(...images);
|
|
40
|
+
return { role: "user", content, timestamp: Date.now() };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createFailureMessage(model: Model<any>, error: unknown, aborted: boolean): AssistantMessage {
|
|
44
|
+
return {
|
|
45
|
+
role: "assistant",
|
|
46
|
+
content: [{ type: "text", text: "" }],
|
|
47
|
+
api: model.api,
|
|
48
|
+
provider: model.provider,
|
|
49
|
+
model: model.id,
|
|
50
|
+
stopReason: aborted ? "aborted" : "error",
|
|
51
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
52
|
+
timestamp: Date.now(),
|
|
53
|
+
usage: {
|
|
54
|
+
input: 0,
|
|
55
|
+
output: 0,
|
|
56
|
+
cacheRead: 0,
|
|
57
|
+
cacheWrite: 0,
|
|
58
|
+
totalTokens: 0,
|
|
59
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cloneStreamOptions(streamOptions?: AgentHarnessStreamOptions): AgentHarnessStreamOptions {
|
|
65
|
+
return {
|
|
66
|
+
...streamOptions,
|
|
67
|
+
headers: streamOptions?.headers ? { ...streamOptions.headers } : undefined,
|
|
68
|
+
metadata: streamOptions?.metadata ? { ...streamOptions.metadata } : undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function findDuplicateNames(names: string[]): string[] {
|
|
73
|
+
const seen = new Set<string>();
|
|
74
|
+
const duplicates = new Set<string>();
|
|
75
|
+
for (const name of names) {
|
|
76
|
+
if (seen.has(name)) duplicates.add(name);
|
|
77
|
+
seen.add(name);
|
|
78
|
+
}
|
|
79
|
+
return [...duplicates];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function applyStreamOptionsPatch(
|
|
83
|
+
base: AgentHarnessStreamOptions,
|
|
84
|
+
patch?: AgentHarnessStreamOptionsPatch,
|
|
85
|
+
): AgentHarnessStreamOptions {
|
|
86
|
+
const result = cloneStreamOptions(base);
|
|
87
|
+
if (!patch) return result;
|
|
88
|
+
|
|
89
|
+
if (Object.hasOwn(patch, "transport")) result.transport = patch.transport;
|
|
90
|
+
if (Object.hasOwn(patch, "timeoutMs")) result.timeoutMs = patch.timeoutMs;
|
|
91
|
+
if (Object.hasOwn(patch, "maxRetries")) result.maxRetries = patch.maxRetries;
|
|
92
|
+
if (Object.hasOwn(patch, "maxRetryDelayMs")) result.maxRetryDelayMs = patch.maxRetryDelayMs;
|
|
93
|
+
if (Object.hasOwn(patch, "cacheRetention")) result.cacheRetention = patch.cacheRetention;
|
|
94
|
+
|
|
95
|
+
if (Object.hasOwn(patch, "headers")) {
|
|
96
|
+
if (patch.headers === undefined) {
|
|
97
|
+
result.headers = undefined;
|
|
98
|
+
} else {
|
|
99
|
+
const headers = { ...(result.headers ?? {}) };
|
|
100
|
+
for (const [key, value] of Object.entries(patch.headers)) {
|
|
101
|
+
if (value === undefined) delete headers[key];
|
|
102
|
+
else headers[key] = value;
|
|
103
|
+
}
|
|
104
|
+
result.headers = Object.keys(headers).length > 0 ? headers : undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Object.hasOwn(patch, "metadata")) {
|
|
109
|
+
if (patch.metadata === undefined) {
|
|
110
|
+
result.metadata = undefined;
|
|
111
|
+
} else {
|
|
112
|
+
const metadata = { ...(result.metadata ?? {}) };
|
|
113
|
+
for (const [key, value] of Object.entries(patch.metadata)) {
|
|
114
|
+
if (value === undefined) delete metadata[key];
|
|
115
|
+
else metadata[key] = value;
|
|
116
|
+
}
|
|
117
|
+
result.metadata = Object.keys(metadata).length > 0 ? metadata : undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const SUBSCRIBER_EVENT_TYPE = "*";
|
|
125
|
+
|
|
126
|
+
type AgentHarnessHandler = (event: any, signal?: AbortSignal) => Promise<any> | any;
|
|
127
|
+
|
|
128
|
+
function normalizeHarnessError(error: unknown, fallbackCode: AgentHarnessError["code"]): AgentHarnessError {
|
|
129
|
+
if (error instanceof AgentHarnessError) return error;
|
|
130
|
+
const cause = toError(error);
|
|
131
|
+
if (cause instanceof SessionError) return new AgentHarnessError("session", cause.message, cause);
|
|
132
|
+
if (cause instanceof CompactionError) return new AgentHarnessError("compaction", cause.message, cause);
|
|
133
|
+
if (cause instanceof BranchSummaryError) return new AgentHarnessError("branch_summary", cause.message, cause);
|
|
134
|
+
return new AgentHarnessError(fallbackCode, cause.message, cause);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function normalizeHookError(error: unknown): AgentHarnessError {
|
|
138
|
+
return normalizeHarnessError(error, "hook");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
interface AgentHarnessTurnState<
|
|
142
|
+
TSkill extends Skill = Skill,
|
|
143
|
+
TPromptTemplate extends PromptTemplate = PromptTemplate,
|
|
144
|
+
TTool extends AgentTool = AgentTool,
|
|
145
|
+
> {
|
|
146
|
+
messages: AgentMessage[];
|
|
147
|
+
resources: AgentHarnessResources<TSkill, TPromptTemplate>;
|
|
148
|
+
streamOptions: AgentHarnessStreamOptions;
|
|
149
|
+
sessionId: string;
|
|
150
|
+
systemPrompt: string;
|
|
151
|
+
model: Model<any>;
|
|
152
|
+
thinkingLevel: ThinkingLevel;
|
|
153
|
+
tools: TTool[];
|
|
154
|
+
activeTools: TTool[];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export class AgentHarness<
|
|
158
|
+
TSkill extends Skill = Skill,
|
|
159
|
+
TPromptTemplate extends PromptTemplate = PromptTemplate,
|
|
160
|
+
TTool extends AgentTool = AgentTool,
|
|
161
|
+
> {
|
|
162
|
+
readonly env: ExecutionEnv;
|
|
163
|
+
private session: Session;
|
|
164
|
+
readonly models: Models;
|
|
165
|
+
private phase: AgentHarnessPhase = "idle";
|
|
166
|
+
private runAbortController?: AbortController;
|
|
167
|
+
private runPromise?: Promise<void>;
|
|
168
|
+
private pendingSessionWrites: PendingSessionWrite[] = [];
|
|
169
|
+
private model: Model<any>;
|
|
170
|
+
private thinkingLevel: ThinkingLevel;
|
|
171
|
+
private systemPrompt: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>["systemPrompt"];
|
|
172
|
+
private streamOptions: AgentHarnessStreamOptions;
|
|
173
|
+
private resources: AgentHarnessResources<TSkill, TPromptTemplate>;
|
|
174
|
+
private tools = new Map<string, TTool>();
|
|
175
|
+
private activeToolNames: string[];
|
|
176
|
+
private steerQueue: UserMessage[] = [];
|
|
177
|
+
private steeringQueueMode: QueueMode;
|
|
178
|
+
private followUpQueue: UserMessage[] = [];
|
|
179
|
+
private followUpQueueMode: QueueMode;
|
|
180
|
+
private nextTurnQueue: AgentMessage[] = [];
|
|
181
|
+
private handlers = new Map<string, Set<AgentHarnessHandler>>();
|
|
182
|
+
|
|
183
|
+
constructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>) {
|
|
184
|
+
this.env = options.env;
|
|
185
|
+
this.session = options.session;
|
|
186
|
+
this.models = options.models;
|
|
187
|
+
this.resources = options.resources ?? {};
|
|
188
|
+
this.streamOptions = cloneStreamOptions(options.streamOptions);
|
|
189
|
+
this.systemPrompt = options.systemPrompt;
|
|
190
|
+
this.validateUniqueNames(
|
|
191
|
+
(options.tools ?? []).map((tool) => tool.name),
|
|
192
|
+
"Duplicate tool name(s)",
|
|
193
|
+
);
|
|
194
|
+
for (const tool of options.tools ?? []) {
|
|
195
|
+
this.tools.set(tool.name, tool);
|
|
196
|
+
}
|
|
197
|
+
this.model = options.model;
|
|
198
|
+
this.thinkingLevel = options.thinkingLevel ?? "off";
|
|
199
|
+
this.activeToolNames = options.activeToolNames
|
|
200
|
+
? [...options.activeToolNames]
|
|
201
|
+
: (options.tools ?? []).map((tool) => tool.name);
|
|
202
|
+
this.validateUniqueNames(this.activeToolNames, "Duplicate active tool name(s)");
|
|
203
|
+
this.validateToolNames(this.activeToolNames);
|
|
204
|
+
this.steeringQueueMode = options.steeringMode ?? "one-at-a-time";
|
|
205
|
+
this.followUpQueueMode = options.followUpMode ?? "one-at-a-time";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private getHandlers(type: string): Set<AgentHarnessHandler> | undefined {
|
|
209
|
+
return this.handlers.get(type);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private async emitOwn(event: AgentHarnessOwnEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {
|
|
213
|
+
for (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {
|
|
214
|
+
try {
|
|
215
|
+
await listener(event, signal);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
throw normalizeHookError(error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private async emitAny(event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal): Promise<void> {
|
|
223
|
+
for (const listener of this.getHandlers(SUBSCRIBER_EVENT_TYPE) ?? []) {
|
|
224
|
+
try {
|
|
225
|
+
await listener(event, signal);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throw normalizeHookError(error);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private async emitHook<TType extends keyof AgentHarnessEventResultMap>(
|
|
233
|
+
event: Extract<AgentHarnessOwnEvent, { type: TType }>,
|
|
234
|
+
): Promise<AgentHarnessEventResultMap[TType] | undefined> {
|
|
235
|
+
const handlers = this.getHandlers(event.type as TType);
|
|
236
|
+
if (!handlers || handlers.size === 0) return undefined;
|
|
237
|
+
let lastResult: AgentHarnessEventResultMap[TType] | undefined;
|
|
238
|
+
for (const handler of handlers) {
|
|
239
|
+
try {
|
|
240
|
+
const result = await handler(event);
|
|
241
|
+
if (result !== undefined) {
|
|
242
|
+
lastResult = result;
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
throw normalizeHookError(error);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return lastResult;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private async emitBeforeProviderRequest(
|
|
252
|
+
model: Model<any>,
|
|
253
|
+
sessionId: string,
|
|
254
|
+
streamOptions: AgentHarnessStreamOptions,
|
|
255
|
+
): Promise<AgentHarnessStreamOptions> {
|
|
256
|
+
const handlers = this.getHandlers("before_provider_request");
|
|
257
|
+
let current = cloneStreamOptions(streamOptions);
|
|
258
|
+
if (!handlers || handlers.size === 0) return current;
|
|
259
|
+
for (const handler of handlers) {
|
|
260
|
+
try {
|
|
261
|
+
const result = await handler({
|
|
262
|
+
type: "before_provider_request",
|
|
263
|
+
model,
|
|
264
|
+
sessionId,
|
|
265
|
+
streamOptions: cloneStreamOptions(current),
|
|
266
|
+
});
|
|
267
|
+
if (result?.streamOptions) {
|
|
268
|
+
current = applyStreamOptionsPatch(current, result.streamOptions);
|
|
269
|
+
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
throw normalizeHookError(error);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return current;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private async emitBeforeProviderPayload(model: Model<any>, payload: unknown): Promise<unknown> {
|
|
278
|
+
const handlers = this.getHandlers("before_provider_payload");
|
|
279
|
+
let current = payload;
|
|
280
|
+
if (!handlers || handlers.size === 0) return current;
|
|
281
|
+
for (const handler of handlers) {
|
|
282
|
+
try {
|
|
283
|
+
const result = await handler({ type: "before_provider_payload", model, payload: current });
|
|
284
|
+
if (result !== undefined) {
|
|
285
|
+
current = result.payload;
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
throw normalizeHookError(error);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return current;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private async emitQueueUpdate(): Promise<void> {
|
|
295
|
+
await this.emitOwn({
|
|
296
|
+
type: "queue_update",
|
|
297
|
+
steer: [...this.steerQueue],
|
|
298
|
+
followUp: [...this.followUpQueue],
|
|
299
|
+
nextTurn: [...this.nextTurnQueue],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private startRunPromise(): () => void {
|
|
304
|
+
let finish = () => {};
|
|
305
|
+
this.runPromise = new Promise<void>((resolve) => {
|
|
306
|
+
finish = resolve;
|
|
307
|
+
});
|
|
308
|
+
return () => {
|
|
309
|
+
this.runPromise = undefined;
|
|
310
|
+
finish();
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private async createTurnState(): Promise<AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>> {
|
|
315
|
+
const context = await this.session.buildContext();
|
|
316
|
+
const resources = this.getResources();
|
|
317
|
+
const sessionMetadata = await this.session.getMetadata();
|
|
318
|
+
const tools = [...this.tools.values()];
|
|
319
|
+
const activeTools = this.activeToolNames
|
|
320
|
+
.map((name) => this.tools.get(name))
|
|
321
|
+
.filter((tool): tool is TTool => tool !== undefined);
|
|
322
|
+
let systemPrompt = "You are a helpful assistant.";
|
|
323
|
+
if (typeof this.systemPrompt === "string") {
|
|
324
|
+
systemPrompt = this.systemPrompt;
|
|
325
|
+
} else if (this.systemPrompt) {
|
|
326
|
+
systemPrompt = await this.systemPrompt({
|
|
327
|
+
env: this.env,
|
|
328
|
+
session: this.session,
|
|
329
|
+
model: this.model,
|
|
330
|
+
thinkingLevel: this.thinkingLevel,
|
|
331
|
+
activeTools,
|
|
332
|
+
resources,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
messages: context.messages,
|
|
337
|
+
resources,
|
|
338
|
+
streamOptions: cloneStreamOptions(this.streamOptions),
|
|
339
|
+
sessionId: sessionMetadata.id,
|
|
340
|
+
systemPrompt,
|
|
341
|
+
model: this.model,
|
|
342
|
+
thinkingLevel: this.thinkingLevel,
|
|
343
|
+
tools,
|
|
344
|
+
activeTools,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private createContext(
|
|
349
|
+
turnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,
|
|
350
|
+
systemPrompt?: string,
|
|
351
|
+
): AgentContext {
|
|
352
|
+
return {
|
|
353
|
+
systemPrompt: systemPrompt ?? turnState.systemPrompt,
|
|
354
|
+
messages: turnState.messages.slice(),
|
|
355
|
+
tools: turnState.activeTools.slice(),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private createStreamFn(getTurnState: () => AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>): StreamFn {
|
|
360
|
+
return async (model, context, streamOptions) => {
|
|
361
|
+
const turnState = getTurnState();
|
|
362
|
+
const snapshotOptions: AgentHarnessStreamOptions = { ...turnState.streamOptions };
|
|
363
|
+
const requestOptions = await this.emitBeforeProviderRequest(model, turnState.sessionId, snapshotOptions);
|
|
364
|
+
return this.models.streamSimple(model, context, {
|
|
365
|
+
cacheRetention: requestOptions.cacheRetention,
|
|
366
|
+
headers: requestOptions.headers,
|
|
367
|
+
maxRetries: requestOptions.maxRetries,
|
|
368
|
+
maxRetryDelayMs: requestOptions.maxRetryDelayMs,
|
|
369
|
+
metadata: requestOptions.metadata,
|
|
370
|
+
onPayload: async (payload) => await this.emitBeforeProviderPayload(model, payload),
|
|
371
|
+
onResponse: async (response) => {
|
|
372
|
+
const headers = { ...(response.headers as Record<string, string>) };
|
|
373
|
+
await this.emitOwn(
|
|
374
|
+
{ type: "after_provider_response", status: response.status, headers },
|
|
375
|
+
streamOptions?.signal,
|
|
376
|
+
);
|
|
377
|
+
},
|
|
378
|
+
reasoning: streamOptions?.reasoning,
|
|
379
|
+
signal: streamOptions?.signal,
|
|
380
|
+
sessionId: turnState.sessionId,
|
|
381
|
+
timeoutMs: requestOptions.timeoutMs,
|
|
382
|
+
transport: requestOptions.transport,
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private async drainQueuedMessages(queue: AgentMessage[], mode: QueueMode): Promise<AgentMessage[]> {
|
|
388
|
+
const messages = mode === "all" ? queue.splice(0) : queue.splice(0, 1);
|
|
389
|
+
if (messages.length === 0) return messages;
|
|
390
|
+
try {
|
|
391
|
+
await this.emitQueueUpdate();
|
|
392
|
+
return messages;
|
|
393
|
+
} catch (error) {
|
|
394
|
+
queue.unshift(...messages);
|
|
395
|
+
throw normalizeHookError(error);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
private createLoopConfig(
|
|
400
|
+
getTurnState: () => AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,
|
|
401
|
+
setTurnState: (turnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => void,
|
|
402
|
+
): AgentLoopConfig {
|
|
403
|
+
const turnState = getTurnState();
|
|
404
|
+
return {
|
|
405
|
+
model: turnState.model,
|
|
406
|
+
reasoning: turnState.thinkingLevel === "off" ? undefined : turnState.thinkingLevel,
|
|
407
|
+
convertToLlm,
|
|
408
|
+
transformContext: async (messages) => {
|
|
409
|
+
const result = await this.emitHook({ type: "context", messages: [...messages] });
|
|
410
|
+
return result?.messages ?? messages;
|
|
411
|
+
},
|
|
412
|
+
beforeToolCall: async ({ toolCall, args }) => {
|
|
413
|
+
const result = await this.emitHook({
|
|
414
|
+
type: "tool_call",
|
|
415
|
+
toolCallId: toolCall.id,
|
|
416
|
+
toolName: toolCall.name,
|
|
417
|
+
input: args as Record<string, unknown>,
|
|
418
|
+
});
|
|
419
|
+
return result ? { block: result.block, reason: result.reason } : undefined;
|
|
420
|
+
},
|
|
421
|
+
afterToolCall: async ({ toolCall, args, result, isError }) => {
|
|
422
|
+
const patch = await this.emitHook({
|
|
423
|
+
type: "tool_result",
|
|
424
|
+
toolCallId: toolCall.id,
|
|
425
|
+
toolName: toolCall.name,
|
|
426
|
+
input: args as Record<string, unknown>,
|
|
427
|
+
content: result.content,
|
|
428
|
+
details: result.details,
|
|
429
|
+
isError,
|
|
430
|
+
});
|
|
431
|
+
return patch
|
|
432
|
+
? { content: patch.content, details: patch.details, isError: patch.isError, terminate: patch.terminate }
|
|
433
|
+
: undefined;
|
|
434
|
+
},
|
|
435
|
+
prepareNextTurn: async () => {
|
|
436
|
+
await this.flushPendingSessionWrites();
|
|
437
|
+
const nextTurnState = await this.createTurnState();
|
|
438
|
+
setTurnState(nextTurnState);
|
|
439
|
+
return {
|
|
440
|
+
context: this.createContext(nextTurnState),
|
|
441
|
+
model: nextTurnState.model,
|
|
442
|
+
thinkingLevel: nextTurnState.thinkingLevel,
|
|
443
|
+
};
|
|
444
|
+
},
|
|
445
|
+
getSteeringMessages: async () => this.drainQueuedMessages(this.steerQueue, this.steeringQueueMode),
|
|
446
|
+
getFollowUpMessages: async () => this.drainQueuedMessages(this.followUpQueue, this.followUpQueueMode),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
private validateUniqueNames(names: string[], message: string): void {
|
|
451
|
+
const duplicates = findDuplicateNames(names);
|
|
452
|
+
if (duplicates.length > 0)
|
|
453
|
+
throw new AgentHarnessError("invalid_argument", `${message}: ${duplicates.join(", ")}`);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private validateToolNames(toolNames: string[], tools: Map<string, TTool> = this.tools): void {
|
|
457
|
+
this.validateUniqueNames(toolNames, "Duplicate active tool name(s)");
|
|
458
|
+
const missing = toolNames.filter((name) => !tools.has(name));
|
|
459
|
+
if (missing.length > 0) throw new AgentHarnessError("invalid_argument", `Unknown tool(s): ${missing.join(", ")}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private async flushPendingSessionWrites(): Promise<void> {
|
|
463
|
+
while (this.pendingSessionWrites.length > 0) {
|
|
464
|
+
const write = this.pendingSessionWrites[0]!;
|
|
465
|
+
if (write.type === "message") {
|
|
466
|
+
await this.session.appendMessage(write.message);
|
|
467
|
+
} else if (write.type === "model_change") {
|
|
468
|
+
await this.session.appendModelChange(write.provider, write.modelId);
|
|
469
|
+
} else if (write.type === "thinking_level_change") {
|
|
470
|
+
await this.session.appendThinkingLevelChange(write.thinkingLevel);
|
|
471
|
+
} else if (write.type === "active_tools_change") {
|
|
472
|
+
await this.session.appendActiveToolsChange(write.activeToolNames);
|
|
473
|
+
} else if (write.type === "custom") {
|
|
474
|
+
await this.session.appendCustomEntry(write.customType, write.data);
|
|
475
|
+
} else if (write.type === "custom_message") {
|
|
476
|
+
await this.session.appendCustomMessageEntry(write.customType, write.content, write.display, write.details);
|
|
477
|
+
} else if (write.type === "label") {
|
|
478
|
+
await this.session.appendLabel(write.targetId, write.label);
|
|
479
|
+
} else if (write.type === "session_info") {
|
|
480
|
+
await this.session.appendSessionName(write.name ?? "");
|
|
481
|
+
} else if (write.type === "leaf") {
|
|
482
|
+
await this.session.getStorage().setLeafId(write.targetId);
|
|
483
|
+
}
|
|
484
|
+
this.pendingSessionWrites.shift();
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
private async handleAgentEvent(event: AgentEvent, signal?: AbortSignal): Promise<void> {
|
|
489
|
+
if (event.type === "message_end") {
|
|
490
|
+
await this.session.appendMessage(event.message);
|
|
491
|
+
await this.emitAny(event, signal);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (event.type === "turn_end") {
|
|
495
|
+
let eventError: unknown;
|
|
496
|
+
try {
|
|
497
|
+
await this.emitAny(event, signal);
|
|
498
|
+
} catch (error) {
|
|
499
|
+
eventError = error;
|
|
500
|
+
}
|
|
501
|
+
const hadPendingMutations = this.pendingSessionWrites.length > 0;
|
|
502
|
+
await this.flushPendingSessionWrites();
|
|
503
|
+
if (eventError) throw eventError;
|
|
504
|
+
await this.emitOwn({ type: "save_point", hadPendingMutations });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (event.type === "agent_end") {
|
|
508
|
+
await this.flushPendingSessionWrites();
|
|
509
|
+
this.phase = "idle";
|
|
510
|
+
await this.emitAny(event, signal);
|
|
511
|
+
await this.emitOwn({ type: "settled", nextTurnCount: this.nextTurnQueue.length }, signal);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
await this.emitAny(event, signal);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private async emitRunFailure(
|
|
518
|
+
model: Model<any>,
|
|
519
|
+
error: unknown,
|
|
520
|
+
aborted: boolean,
|
|
521
|
+
signal: AbortSignal,
|
|
522
|
+
): Promise<AgentMessage[]> {
|
|
523
|
+
const failureMessage = createFailureMessage(model, error, aborted);
|
|
524
|
+
await this.handleAgentEvent({ type: "message_start", message: failureMessage }, signal);
|
|
525
|
+
await this.handleAgentEvent({ type: "message_end", message: failureMessage }, signal);
|
|
526
|
+
await this.handleAgentEvent({ type: "turn_end", message: failureMessage, toolResults: [] }, signal);
|
|
527
|
+
await this.handleAgentEvent({ type: "agent_end", messages: [failureMessage] }, signal);
|
|
528
|
+
return [failureMessage];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private async executeTurn(
|
|
532
|
+
turnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>,
|
|
533
|
+
text: string,
|
|
534
|
+
options?: { images?: ImageContent[] },
|
|
535
|
+
): Promise<AssistantMessage> {
|
|
536
|
+
let activeTurnState = turnState;
|
|
537
|
+
let messages: AgentMessage[] = [createUserMessage(text, options?.images)];
|
|
538
|
+
if (this.nextTurnQueue.length > 0) {
|
|
539
|
+
const queuedMessages = this.nextTurnQueue.splice(0);
|
|
540
|
+
try {
|
|
541
|
+
await this.emitQueueUpdate();
|
|
542
|
+
} catch (error) {
|
|
543
|
+
this.nextTurnQueue.unshift(...queuedMessages);
|
|
544
|
+
throw normalizeHookError(error);
|
|
545
|
+
}
|
|
546
|
+
messages = [...queuedMessages, messages[0]!];
|
|
547
|
+
}
|
|
548
|
+
const beforeResult = await this.emitHook({
|
|
549
|
+
type: "before_agent_start",
|
|
550
|
+
prompt: text,
|
|
551
|
+
images: options?.images,
|
|
552
|
+
systemPrompt: turnState.systemPrompt,
|
|
553
|
+
resources: turnState.resources,
|
|
554
|
+
});
|
|
555
|
+
if (beforeResult?.messages) messages = [...messages, ...beforeResult.messages];
|
|
556
|
+
|
|
557
|
+
const abortController = new AbortController();
|
|
558
|
+
const getTurnState = () => activeTurnState;
|
|
559
|
+
const setTurnState = (nextTurnState: AgentHarnessTurnState<TSkill, TPromptTemplate, TTool>) => {
|
|
560
|
+
activeTurnState = nextTurnState;
|
|
561
|
+
};
|
|
562
|
+
this.runAbortController = abortController;
|
|
563
|
+
const runResultPromise = (async () => {
|
|
564
|
+
try {
|
|
565
|
+
return await runAgentLoop(
|
|
566
|
+
messages,
|
|
567
|
+
this.createContext(turnState, beforeResult?.systemPrompt),
|
|
568
|
+
this.createLoopConfig(getTurnState, setTurnState),
|
|
569
|
+
(event) => this.handleAgentEvent(event, abortController.signal),
|
|
570
|
+
abortController.signal,
|
|
571
|
+
this.createStreamFn(getTurnState),
|
|
572
|
+
);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
try {
|
|
575
|
+
return await this.emitRunFailure(
|
|
576
|
+
activeTurnState.model,
|
|
577
|
+
error,
|
|
578
|
+
abortController.signal.aborted,
|
|
579
|
+
abortController.signal,
|
|
580
|
+
);
|
|
581
|
+
} catch (failureError) {
|
|
582
|
+
const cause = new AggregateError(
|
|
583
|
+
[toError(error), toError(failureError)],
|
|
584
|
+
"Agent run failed and failure reporting failed",
|
|
585
|
+
);
|
|
586
|
+
throw new AgentHarnessError("unknown", cause.message, cause);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
})();
|
|
590
|
+
try {
|
|
591
|
+
const newMessages = await runResultPromise;
|
|
592
|
+
for (let i = newMessages.length - 1; i >= 0; i--) {
|
|
593
|
+
const message = newMessages[i]!;
|
|
594
|
+
if (message.role === "assistant") {
|
|
595
|
+
return message;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
throw new AgentHarnessError("invalid_state", "AgentHarness prompt completed without an assistant message");
|
|
599
|
+
} finally {
|
|
600
|
+
try {
|
|
601
|
+
await this.flushPendingSessionWrites();
|
|
602
|
+
} finally {
|
|
603
|
+
this.runAbortController = undefined;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async prompt(text: string, options?: { images?: ImageContent[] }): Promise<AssistantMessage> {
|
|
609
|
+
if (this.phase !== "idle") throw new AgentHarnessError("busy", "AgentHarness is busy");
|
|
610
|
+
this.phase = "turn";
|
|
611
|
+
const finishRunPromise = this.startRunPromise();
|
|
612
|
+
try {
|
|
613
|
+
const turnState = await this.createTurnState();
|
|
614
|
+
return await this.executeTurn(turnState, text, options);
|
|
615
|
+
} catch (error) {
|
|
616
|
+
this.phase = "idle";
|
|
617
|
+
throw normalizeHarnessError(error, "unknown");
|
|
618
|
+
} finally {
|
|
619
|
+
finishRunPromise();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async skill(name: string, additionalInstructions?: string): Promise<AssistantMessage> {
|
|
624
|
+
if (this.phase !== "idle") throw new AgentHarnessError("busy", "AgentHarness is busy");
|
|
625
|
+
this.phase = "turn";
|
|
626
|
+
const finishRunPromise = this.startRunPromise();
|
|
627
|
+
try {
|
|
628
|
+
const turnState = await this.createTurnState();
|
|
629
|
+
const skill = (turnState.resources.skills ?? []).find((candidate) => candidate.name === name);
|
|
630
|
+
if (!skill) throw new AgentHarnessError("invalid_argument", `Unknown skill: ${name}`);
|
|
631
|
+
return await this.executeTurn(turnState, formatSkillInvocation(skill, additionalInstructions));
|
|
632
|
+
} catch (error) {
|
|
633
|
+
this.phase = "idle";
|
|
634
|
+
throw normalizeHarnessError(error, "unknown");
|
|
635
|
+
} finally {
|
|
636
|
+
finishRunPromise();
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
async promptFromTemplate(name: string, args: string[] = []): Promise<AssistantMessage> {
|
|
641
|
+
if (this.phase !== "idle") throw new AgentHarnessError("busy", "AgentHarness is busy");
|
|
642
|
+
this.phase = "turn";
|
|
643
|
+
const finishRunPromise = this.startRunPromise();
|
|
644
|
+
try {
|
|
645
|
+
const turnState = await this.createTurnState();
|
|
646
|
+
const template = (turnState.resources.promptTemplates ?? []).find((candidate) => candidate.name === name);
|
|
647
|
+
if (!template) throw new AgentHarnessError("invalid_argument", `Unknown prompt template: ${name}`);
|
|
648
|
+
return await this.executeTurn(turnState, formatPromptTemplateInvocation(template, args));
|
|
649
|
+
} catch (error) {
|
|
650
|
+
this.phase = "idle";
|
|
651
|
+
throw normalizeHarnessError(error, "unknown");
|
|
652
|
+
} finally {
|
|
653
|
+
finishRunPromise();
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async steer(text: string, options?: { images?: ImageContent[] }): Promise<void> {
|
|
658
|
+
if (this.phase === "idle") throw new AgentHarnessError("invalid_state", "Cannot steer while idle");
|
|
659
|
+
this.steerQueue.push(createUserMessage(text, options?.images));
|
|
660
|
+
await this.emitQueueUpdate();
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async followUp(text: string, options?: { images?: ImageContent[] }): Promise<void> {
|
|
664
|
+
if (this.phase === "idle") throw new AgentHarnessError("invalid_state", "Cannot follow up while idle");
|
|
665
|
+
this.followUpQueue.push(createUserMessage(text, options?.images));
|
|
666
|
+
await this.emitQueueUpdate();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
async nextTurn(text: string, options?: { images?: ImageContent[] }): Promise<void> {
|
|
670
|
+
this.nextTurnQueue.push(createUserMessage(text, options?.images));
|
|
671
|
+
await this.emitQueueUpdate();
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async appendMessage(message: AgentMessage): Promise<void> {
|
|
675
|
+
try {
|
|
676
|
+
if (this.phase === "idle") {
|
|
677
|
+
await this.session.appendMessage(message);
|
|
678
|
+
} else {
|
|
679
|
+
this.pendingSessionWrites.push({ type: "message", message });
|
|
680
|
+
}
|
|
681
|
+
} catch (error) {
|
|
682
|
+
throw normalizeHarnessError(error, "session");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async compact(
|
|
687
|
+
customInstructions?: string,
|
|
688
|
+
): Promise<{ summary: string; firstKeptEntryId: string; tokensBefore: number; details?: unknown }> {
|
|
689
|
+
if (this.phase !== "idle") throw new AgentHarnessError("busy", "compact() requires idle harness");
|
|
690
|
+
this.phase = "compaction";
|
|
691
|
+
try {
|
|
692
|
+
const model = this.model;
|
|
693
|
+
if (!model) throw new AgentHarnessError("invalid_state", "No model set for compaction");
|
|
694
|
+
const branchEntries = await this.session.getBranch();
|
|
695
|
+
const preparationResult = prepareCompaction(branchEntries, DEFAULT_COMPACTION_SETTINGS);
|
|
696
|
+
if (!preparationResult.ok) throw preparationResult.error;
|
|
697
|
+
const preparation = preparationResult.value;
|
|
698
|
+
if (!preparation) throw new AgentHarnessError("compaction", "Nothing to compact");
|
|
699
|
+
const hookResult = await this.emitHook({
|
|
700
|
+
type: "session_before_compact",
|
|
701
|
+
preparation,
|
|
702
|
+
branchEntries,
|
|
703
|
+
customInstructions,
|
|
704
|
+
signal: new AbortController().signal,
|
|
705
|
+
});
|
|
706
|
+
if (hookResult?.cancel) throw new AgentHarnessError("compaction", "Compaction cancelled");
|
|
707
|
+
const provided = hookResult?.compaction;
|
|
708
|
+
const compactResult = provided
|
|
709
|
+
? { ok: true as const, value: provided }
|
|
710
|
+
: await compact(preparation, this.models, model, customInstructions, undefined, this.thinkingLevel);
|
|
711
|
+
if (!compactResult.ok) throw compactResult.error;
|
|
712
|
+
const result = compactResult.value;
|
|
713
|
+
const entryId = await this.session.appendCompaction(
|
|
714
|
+
result.summary,
|
|
715
|
+
result.firstKeptEntryId,
|
|
716
|
+
result.tokensBefore,
|
|
717
|
+
result.details,
|
|
718
|
+
provided !== undefined,
|
|
719
|
+
);
|
|
720
|
+
const entry = await this.session.getEntry(entryId);
|
|
721
|
+
if (entry?.type === "compaction") {
|
|
722
|
+
await this.emitOwn({ type: "session_compact", compactionEntry: entry, fromHook: provided !== undefined });
|
|
723
|
+
}
|
|
724
|
+
return result;
|
|
725
|
+
} catch (error) {
|
|
726
|
+
throw normalizeHarnessError(error, "compaction");
|
|
727
|
+
} finally {
|
|
728
|
+
this.phase = "idle";
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async navigateTree(
|
|
733
|
+
targetId: string,
|
|
734
|
+
options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string },
|
|
735
|
+
): Promise<NavigateTreeResult> {
|
|
736
|
+
if (this.phase !== "idle") throw new AgentHarnessError("busy", "navigateTree() requires idle harness");
|
|
737
|
+
this.phase = "branch_summary";
|
|
738
|
+
try {
|
|
739
|
+
const oldLeafId = await this.session.getLeafId();
|
|
740
|
+
if (oldLeafId === targetId) return { cancelled: false };
|
|
741
|
+
const targetEntry = await this.session.getEntry(targetId);
|
|
742
|
+
if (!targetEntry) throw new AgentHarnessError("invalid_argument", `Entry ${targetId} not found`);
|
|
743
|
+
const { entries, commonAncestorId } = await collectEntriesForBranchSummary(this.session, oldLeafId, targetId);
|
|
744
|
+
const preparation = {
|
|
745
|
+
targetId,
|
|
746
|
+
oldLeafId,
|
|
747
|
+
commonAncestorId,
|
|
748
|
+
entriesToSummarize: entries,
|
|
749
|
+
userWantsSummary: options?.summarize ?? false,
|
|
750
|
+
customInstructions: options?.customInstructions,
|
|
751
|
+
replaceInstructions: options?.replaceInstructions,
|
|
752
|
+
label: options?.label,
|
|
753
|
+
};
|
|
754
|
+
const signal = new AbortController().signal;
|
|
755
|
+
const hookResult = await this.emitHook({ type: "session_before_tree", preparation, signal });
|
|
756
|
+
if (hookResult?.cancel) return { cancelled: true };
|
|
757
|
+
let summaryEntry: NavigateTreeResult["summaryEntry"];
|
|
758
|
+
let summaryText: string | undefined = hookResult?.summary?.summary;
|
|
759
|
+
let summaryDetails: unknown = hookResult?.summary?.details;
|
|
760
|
+
if (!summaryText && options?.summarize && entries.length > 0) {
|
|
761
|
+
const model = this.model;
|
|
762
|
+
if (!model) throw new AgentHarnessError("invalid_state", "No model set for branch summary");
|
|
763
|
+
const branchSummary = await generateBranchSummary(entries, {
|
|
764
|
+
models: this.models,
|
|
765
|
+
model,
|
|
766
|
+
signal: new AbortController().signal,
|
|
767
|
+
customInstructions: hookResult?.customInstructions ?? options?.customInstructions,
|
|
768
|
+
replaceInstructions: hookResult?.replaceInstructions ?? options?.replaceInstructions,
|
|
769
|
+
});
|
|
770
|
+
if (!branchSummary.ok) {
|
|
771
|
+
if (branchSummary.error.code === "aborted") return { cancelled: true };
|
|
772
|
+
throw new AgentHarnessError("branch_summary", branchSummary.error.message, branchSummary.error);
|
|
773
|
+
}
|
|
774
|
+
summaryText = branchSummary.value.summary;
|
|
775
|
+
summaryDetails = {
|
|
776
|
+
readFiles: branchSummary.value.readFiles,
|
|
777
|
+
modifiedFiles: branchSummary.value.modifiedFiles,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
let editorText: string | undefined;
|
|
781
|
+
let newLeafId: string | null;
|
|
782
|
+
if (targetEntry.type === "message" && targetEntry.message.role === "user") {
|
|
783
|
+
newLeafId = targetEntry.parentId;
|
|
784
|
+
const content = targetEntry.message.content;
|
|
785
|
+
editorText =
|
|
786
|
+
typeof content === "string"
|
|
787
|
+
? content
|
|
788
|
+
: content
|
|
789
|
+
.filter((c): c is { readonly type: "text"; readonly text: string } => c.type === "text")
|
|
790
|
+
.map((c) => c.text)
|
|
791
|
+
.join("");
|
|
792
|
+
} else if (targetEntry.type === "custom_message") {
|
|
793
|
+
newLeafId = targetEntry.parentId;
|
|
794
|
+
editorText =
|
|
795
|
+
typeof targetEntry.content === "string"
|
|
796
|
+
? targetEntry.content
|
|
797
|
+
: targetEntry.content
|
|
798
|
+
.filter((c): c is { readonly type: "text"; readonly text: string } => c.type === "text")
|
|
799
|
+
.map((c) => c.text)
|
|
800
|
+
.join("");
|
|
801
|
+
} else {
|
|
802
|
+
newLeafId = targetId;
|
|
803
|
+
}
|
|
804
|
+
const summaryId = await this.session.moveTo(
|
|
805
|
+
newLeafId,
|
|
806
|
+
summaryText
|
|
807
|
+
? { summary: summaryText, details: summaryDetails, fromHook: hookResult?.summary !== undefined }
|
|
808
|
+
: undefined,
|
|
809
|
+
);
|
|
810
|
+
if (summaryId) {
|
|
811
|
+
const entry = await this.session.getEntry(summaryId);
|
|
812
|
+
if (entry?.type === "branch_summary") summaryEntry = entry;
|
|
813
|
+
}
|
|
814
|
+
await this.emitOwn({
|
|
815
|
+
type: "session_tree",
|
|
816
|
+
newLeafId: await this.session.getLeafId(),
|
|
817
|
+
oldLeafId,
|
|
818
|
+
summaryEntry,
|
|
819
|
+
fromHook: hookResult?.summary !== undefined,
|
|
820
|
+
});
|
|
821
|
+
return { cancelled: false, editorText, summaryEntry };
|
|
822
|
+
} catch (error) {
|
|
823
|
+
throw normalizeHarnessError(error, "branch_summary");
|
|
824
|
+
} finally {
|
|
825
|
+
this.phase = "idle";
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
getModel(): Model<any> {
|
|
830
|
+
return this.model;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async setModel(model: Model<any>): Promise<void> {
|
|
834
|
+
try {
|
|
835
|
+
const previousModel = this.model;
|
|
836
|
+
if (this.phase === "idle") {
|
|
837
|
+
await this.session.appendModelChange(model.provider, model.id);
|
|
838
|
+
} else {
|
|
839
|
+
this.pendingSessionWrites.push({ type: "model_change", provider: model.provider, modelId: model.id });
|
|
840
|
+
}
|
|
841
|
+
this.model = model;
|
|
842
|
+
await this.emitOwn({ type: "model_update", model, previousModel, source: "set" });
|
|
843
|
+
} catch (error) {
|
|
844
|
+
throw normalizeHarnessError(error, "session");
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
getThinkingLevel(): ThinkingLevel {
|
|
849
|
+
return this.thinkingLevel;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
async setThinkingLevel(level: ThinkingLevel): Promise<void> {
|
|
853
|
+
try {
|
|
854
|
+
const previousLevel = this.thinkingLevel;
|
|
855
|
+
if (this.phase === "idle") {
|
|
856
|
+
await this.session.appendThinkingLevelChange(level);
|
|
857
|
+
} else {
|
|
858
|
+
this.pendingSessionWrites.push({ type: "thinking_level_change", thinkingLevel: level });
|
|
859
|
+
}
|
|
860
|
+
this.thinkingLevel = level;
|
|
861
|
+
await this.emitOwn({ type: "thinking_level_update", level, previousLevel });
|
|
862
|
+
} catch (error) {
|
|
863
|
+
throw normalizeHarnessError(error, "session");
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
getTools(): TTool[] {
|
|
868
|
+
return [...this.tools.values()];
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async setTools(tools: TTool[], activeToolNames?: string[]): Promise<void> {
|
|
872
|
+
try {
|
|
873
|
+
this.validateUniqueNames(
|
|
874
|
+
tools.map((tool) => tool.name),
|
|
875
|
+
"Duplicate tool name(s)",
|
|
876
|
+
);
|
|
877
|
+
const nextTools = new Map(tools.map((tool) => [tool.name, tool]));
|
|
878
|
+
const nextActiveToolNames = activeToolNames ? [...activeToolNames] : this.activeToolNames;
|
|
879
|
+
this.validateToolNames(nextActiveToolNames, nextTools);
|
|
880
|
+
const previousToolNames = [...this.tools.keys()];
|
|
881
|
+
const previousActiveToolNames = [...this.activeToolNames];
|
|
882
|
+
if (this.phase === "idle") {
|
|
883
|
+
await this.session.appendActiveToolsChange(nextActiveToolNames);
|
|
884
|
+
} else {
|
|
885
|
+
this.pendingSessionWrites.push({ type: "active_tools_change", activeToolNames: [...nextActiveToolNames] });
|
|
886
|
+
}
|
|
887
|
+
this.tools = nextTools;
|
|
888
|
+
this.activeToolNames = [...nextActiveToolNames];
|
|
889
|
+
await this.emitOwn({
|
|
890
|
+
type: "tools_update",
|
|
891
|
+
toolNames: [...this.tools.keys()],
|
|
892
|
+
previousToolNames,
|
|
893
|
+
activeToolNames: [...this.activeToolNames],
|
|
894
|
+
previousActiveToolNames,
|
|
895
|
+
source: "set",
|
|
896
|
+
});
|
|
897
|
+
} catch (error) {
|
|
898
|
+
throw normalizeHarnessError(error, "invalid_argument");
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
getActiveTools(): TTool[] {
|
|
903
|
+
return this.activeToolNames.map((name) => this.tools.get(name)!);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
async setActiveTools(toolNames: string[]): Promise<void> {
|
|
907
|
+
try {
|
|
908
|
+
this.validateToolNames(toolNames);
|
|
909
|
+
const previousToolNames = [...this.tools.keys()];
|
|
910
|
+
const previousActiveToolNames = [...this.activeToolNames];
|
|
911
|
+
if (this.phase === "idle") {
|
|
912
|
+
await this.session.appendActiveToolsChange(toolNames);
|
|
913
|
+
} else {
|
|
914
|
+
this.pendingSessionWrites.push({ type: "active_tools_change", activeToolNames: [...toolNames] });
|
|
915
|
+
}
|
|
916
|
+
this.activeToolNames = [...toolNames];
|
|
917
|
+
await this.emitOwn({
|
|
918
|
+
type: "tools_update",
|
|
919
|
+
toolNames: [...this.tools.keys()],
|
|
920
|
+
previousToolNames,
|
|
921
|
+
activeToolNames: [...this.activeToolNames],
|
|
922
|
+
previousActiveToolNames,
|
|
923
|
+
source: "set",
|
|
924
|
+
});
|
|
925
|
+
} catch (error) {
|
|
926
|
+
throw normalizeHarnessError(error, "invalid_argument");
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
getSteeringMode(): QueueMode {
|
|
931
|
+
return this.steeringQueueMode;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
async setSteeringMode(mode: QueueMode): Promise<void> {
|
|
935
|
+
this.steeringQueueMode = mode;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
getFollowUpMode(): QueueMode {
|
|
939
|
+
return this.followUpQueueMode;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
async setFollowUpMode(mode: QueueMode): Promise<void> {
|
|
943
|
+
this.followUpQueueMode = mode;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
getResources(): AgentHarnessResources<TSkill, TPromptTemplate> {
|
|
947
|
+
return {
|
|
948
|
+
skills: this.resources.skills?.slice(),
|
|
949
|
+
promptTemplates: this.resources.promptTemplates?.slice(),
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async setResources(resources: AgentHarnessResources<TSkill, TPromptTemplate>): Promise<void> {
|
|
954
|
+
const previousResources = this.getResources();
|
|
955
|
+
this.resources = {
|
|
956
|
+
skills: resources.skills?.slice(),
|
|
957
|
+
promptTemplates: resources.promptTemplates?.slice(),
|
|
958
|
+
};
|
|
959
|
+
await this.emitOwn({ type: "resources_update", resources: this.getResources(), previousResources });
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
getStreamOptions(): AgentHarnessStreamOptions {
|
|
963
|
+
return cloneStreamOptions(this.streamOptions);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
async setStreamOptions(streamOptions: AgentHarnessStreamOptions): Promise<void> {
|
|
967
|
+
this.streamOptions = cloneStreamOptions(streamOptions);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async abort(): Promise<AbortResult> {
|
|
971
|
+
const clearedSteer = [...this.steerQueue];
|
|
972
|
+
const clearedFollowUp = [...this.followUpQueue];
|
|
973
|
+
this.steerQueue = [];
|
|
974
|
+
this.followUpQueue = [];
|
|
975
|
+
this.runAbortController?.abort();
|
|
976
|
+
const errors: Error[] = [];
|
|
977
|
+
try {
|
|
978
|
+
await this.emitQueueUpdate();
|
|
979
|
+
} catch (error) {
|
|
980
|
+
errors.push(toError(error));
|
|
981
|
+
}
|
|
982
|
+
try {
|
|
983
|
+
await this.waitForIdle();
|
|
984
|
+
} catch (error) {
|
|
985
|
+
errors.push(toError(error));
|
|
986
|
+
}
|
|
987
|
+
try {
|
|
988
|
+
await this.emitOwn({ type: "abort", clearedSteer, clearedFollowUp });
|
|
989
|
+
} catch (error) {
|
|
990
|
+
errors.push(toError(error));
|
|
991
|
+
}
|
|
992
|
+
if (errors.length > 0) {
|
|
993
|
+
const cause = errors.length === 1 ? errors[0]! : new AggregateError(errors, "Abort completed with errors");
|
|
994
|
+
throw normalizeHarnessError(cause, "hook");
|
|
995
|
+
}
|
|
996
|
+
return { clearedSteer, clearedFollowUp };
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async waitForIdle(): Promise<void> {
|
|
1000
|
+
await this.runPromise;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
subscribe(
|
|
1004
|
+
listener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void,
|
|
1005
|
+
): () => void {
|
|
1006
|
+
let handlers = this.handlers.get(SUBSCRIBER_EVENT_TYPE);
|
|
1007
|
+
if (!handlers) {
|
|
1008
|
+
handlers = new Set();
|
|
1009
|
+
this.handlers.set(SUBSCRIBER_EVENT_TYPE, handlers);
|
|
1010
|
+
}
|
|
1011
|
+
handlers.add(listener as AgentHarnessHandler);
|
|
1012
|
+
return () => handlers!.delete(listener as AgentHarnessHandler);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
on<TType extends keyof AgentHarnessEventResultMap>(
|
|
1016
|
+
type: TType,
|
|
1017
|
+
handler: (
|
|
1018
|
+
event: Extract<AgentHarnessOwnEvent, { type: TType }>,
|
|
1019
|
+
) => Promise<AgentHarnessEventResultMap[TType]> | AgentHarnessEventResultMap[TType],
|
|
1020
|
+
): () => void {
|
|
1021
|
+
let handlers = this.handlers.get(type);
|
|
1022
|
+
if (!handlers) {
|
|
1023
|
+
handlers = new Set();
|
|
1024
|
+
this.handlers.set(type, handlers);
|
|
1025
|
+
}
|
|
1026
|
+
handlers.add(handler as AgentHarnessHandler);
|
|
1027
|
+
return () => handlers!.delete(handler as AgentHarnessHandler);
|
|
1028
|
+
}
|
|
1029
|
+
}
|