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,135 @@
|
|
|
1
|
+
import { type ExecutionEnv, ExecutionError, err, ok, type Result, type ShellExecOptions, toError } from "../types.ts";
|
|
2
|
+
import { DEFAULT_MAX_BYTES, truncateTail } from "./truncate.ts";
|
|
3
|
+
|
|
4
|
+
export interface ShellCaptureOptions extends Omit<ShellExecOptions, "onStdout" | "onStderr"> {
|
|
5
|
+
onChunk?: (chunk: string) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface ShellCaptureResult {
|
|
9
|
+
output: string;
|
|
10
|
+
exitCode: number | undefined;
|
|
11
|
+
cancelled: boolean;
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
fullOutputPath?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toExecutionError(error: unknown): ExecutionError {
|
|
17
|
+
if (error instanceof ExecutionError) return error;
|
|
18
|
+
const cause = toError(error);
|
|
19
|
+
return new ExecutionError("unknown", cause.message, cause);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function sanitizeBinaryOutput(str: string): string {
|
|
23
|
+
return Array.from(str)
|
|
24
|
+
.filter((char) => {
|
|
25
|
+
const code = char.codePointAt(0);
|
|
26
|
+
if (code === undefined) return false;
|
|
27
|
+
if (code === 0x09 || code === 0x0a || code === 0x0d) return true;
|
|
28
|
+
if (code <= 0x1f) return false;
|
|
29
|
+
if (code >= 0xfff9 && code <= 0xfffb) return false;
|
|
30
|
+
return true;
|
|
31
|
+
})
|
|
32
|
+
.join("");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function executeShellWithCapture(
|
|
36
|
+
env: ExecutionEnv,
|
|
37
|
+
command: string,
|
|
38
|
+
options?: ShellCaptureOptions,
|
|
39
|
+
): Promise<Result<ShellCaptureResult, ExecutionError>> {
|
|
40
|
+
const outputChunks: string[] = [];
|
|
41
|
+
let outputBytes = 0;
|
|
42
|
+
const maxOutputBytes = DEFAULT_MAX_BYTES * 2;
|
|
43
|
+
const encoder = new TextEncoder();
|
|
44
|
+
|
|
45
|
+
let totalBytes = 0;
|
|
46
|
+
let fullOutputPath: string | undefined;
|
|
47
|
+
let writeChain: Promise<Result<void, ExecutionError>> = Promise.resolve(ok(undefined));
|
|
48
|
+
let captureError: ExecutionError | undefined;
|
|
49
|
+
|
|
50
|
+
const appendFullOutput = (text: string): void => {
|
|
51
|
+
if (!fullOutputPath || captureError) return;
|
|
52
|
+
const path = fullOutputPath;
|
|
53
|
+
writeChain = writeChain.then(async (previous) => {
|
|
54
|
+
if (!previous.ok) return previous;
|
|
55
|
+
const appendResult = await env.appendFile(path, text, options?.abortSignal);
|
|
56
|
+
return appendResult.ok ? ok(undefined) : err(toExecutionError(appendResult.error));
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const ensureFullOutputFile = (initialContent: string): void => {
|
|
61
|
+
if (fullOutputPath || captureError) return;
|
|
62
|
+
writeChain = writeChain.then(async (previous) => {
|
|
63
|
+
if (!previous.ok) return previous;
|
|
64
|
+
const tempFile = await env.createTempFile({
|
|
65
|
+
prefix: "bash-",
|
|
66
|
+
suffix: ".log",
|
|
67
|
+
abortSignal: options?.abortSignal,
|
|
68
|
+
});
|
|
69
|
+
if (!tempFile.ok) return err(toExecutionError(tempFile.error));
|
|
70
|
+
fullOutputPath = tempFile.value;
|
|
71
|
+
const appendResult = await env.appendFile(tempFile.value, initialContent, options?.abortSignal);
|
|
72
|
+
return appendResult.ok ? ok(undefined) : err(toExecutionError(appendResult.error));
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const onChunk = (chunk: string) => {
|
|
77
|
+
try {
|
|
78
|
+
totalBytes += encoder.encode(chunk).byteLength;
|
|
79
|
+
const text = sanitizeBinaryOutput(chunk).replace(/\r/g, "");
|
|
80
|
+
if (totalBytes > DEFAULT_MAX_BYTES && !fullOutputPath) {
|
|
81
|
+
ensureFullOutputFile(outputChunks.join("") + text);
|
|
82
|
+
} else {
|
|
83
|
+
appendFullOutput(text);
|
|
84
|
+
}
|
|
85
|
+
outputChunks.push(text);
|
|
86
|
+
outputBytes += text.length;
|
|
87
|
+
while (outputBytes > maxOutputBytes && outputChunks.length > 1) {
|
|
88
|
+
const removed = outputChunks.shift()!;
|
|
89
|
+
outputBytes -= removed.length;
|
|
90
|
+
}
|
|
91
|
+
options?.onChunk?.(text);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
captureError = toExecutionError(error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await env.exec(command, {
|
|
99
|
+
...(options ?? {}),
|
|
100
|
+
onStdout: onChunk,
|
|
101
|
+
onStderr: onChunk,
|
|
102
|
+
});
|
|
103
|
+
const tailOutput = outputChunks.join("");
|
|
104
|
+
const truncationResult = truncateTail(tailOutput);
|
|
105
|
+
if (truncationResult.truncated && !fullOutputPath) {
|
|
106
|
+
ensureFullOutputFile(tailOutput);
|
|
107
|
+
}
|
|
108
|
+
const writeResult = await writeChain;
|
|
109
|
+
if (!writeResult.ok) return err(writeResult.error);
|
|
110
|
+
if (captureError) return err(captureError);
|
|
111
|
+
|
|
112
|
+
if (!result.ok) {
|
|
113
|
+
if (result.error.code === "aborted" || options?.abortSignal?.aborted) {
|
|
114
|
+
return ok({
|
|
115
|
+
output: truncationResult.truncated ? truncationResult.content : tailOutput,
|
|
116
|
+
exitCode: undefined,
|
|
117
|
+
cancelled: true,
|
|
118
|
+
truncated: truncationResult.truncated,
|
|
119
|
+
fullOutputPath,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return err(result.error);
|
|
123
|
+
}
|
|
124
|
+
const cancelled = options?.abortSignal?.aborted ?? false;
|
|
125
|
+
return ok({
|
|
126
|
+
output: truncationResult.truncated ? truncationResult.content : tailOutput,
|
|
127
|
+
exitCode: cancelled ? undefined : result.value.exitCode,
|
|
128
|
+
cancelled,
|
|
129
|
+
truncated: truncationResult.truncated,
|
|
130
|
+
fullOutputPath,
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
return err(toExecutionError(error));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared truncation utilities for tool outputs.
|
|
3
|
+
*
|
|
4
|
+
* Truncation is based on two independent limits - whichever is hit first wins:
|
|
5
|
+
* - Line limit (default: 2000 lines)
|
|
6
|
+
* - Byte limit (default: 50KB)
|
|
7
|
+
*
|
|
8
|
+
* Never returns partial lines (except bash tail truncation edge case).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const DEFAULT_MAX_LINES = 2000;
|
|
12
|
+
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
13
|
+
export const GREP_MAX_LINE_LENGTH = 500; // Max chars per grep match line
|
|
14
|
+
|
|
15
|
+
export interface TruncationResult {
|
|
16
|
+
/** The truncated content */
|
|
17
|
+
content: string;
|
|
18
|
+
/** Whether truncation occurred */
|
|
19
|
+
truncated: boolean;
|
|
20
|
+
/** Which limit was hit: "lines", "bytes", or null if not truncated */
|
|
21
|
+
truncatedBy: "lines" | "bytes" | null;
|
|
22
|
+
/** Total number of lines in the original content */
|
|
23
|
+
totalLines: number;
|
|
24
|
+
/** Total number of bytes in the original content */
|
|
25
|
+
totalBytes: number;
|
|
26
|
+
/** Number of complete lines in the truncated output */
|
|
27
|
+
outputLines: number;
|
|
28
|
+
/** Number of bytes in the truncated output */
|
|
29
|
+
outputBytes: number;
|
|
30
|
+
/** Whether the last line was partially truncated (only for tail truncation edge case) */
|
|
31
|
+
lastLinePartial: boolean;
|
|
32
|
+
/** Whether the first line exceeded the byte limit (for head truncation) */
|
|
33
|
+
firstLineExceedsLimit: boolean;
|
|
34
|
+
/** The max lines limit that was applied */
|
|
35
|
+
maxLines: number;
|
|
36
|
+
/** The max bytes limit that was applied */
|
|
37
|
+
maxBytes: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface TruncationOptions {
|
|
41
|
+
/** Maximum number of lines (default: 2000) */
|
|
42
|
+
maxLines?: number;
|
|
43
|
+
/** Maximum number of bytes (default: 50KB) */
|
|
44
|
+
maxBytes?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface RuntimeBuffer {
|
|
48
|
+
byteLength(content: string, encoding: "utf8"): number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const runtimeBuffer = (globalThis as { Buffer?: RuntimeBuffer }).Buffer;
|
|
52
|
+
const nonAsciiPattern = /[^\x00-\x7f]/;
|
|
53
|
+
|
|
54
|
+
function utf8ByteLength(content: string): number {
|
|
55
|
+
if (runtimeBuffer) return runtimeBuffer.byteLength(content, "utf8");
|
|
56
|
+
|
|
57
|
+
const firstNonAscii = content.search(nonAsciiPattern);
|
|
58
|
+
if (firstNonAscii === -1) return content.length;
|
|
59
|
+
|
|
60
|
+
let bytes = firstNonAscii;
|
|
61
|
+
for (let i = firstNonAscii; i < content.length; i++) {
|
|
62
|
+
const code = content.charCodeAt(i);
|
|
63
|
+
if (code <= 0x7f) {
|
|
64
|
+
bytes += 1;
|
|
65
|
+
} else if (code <= 0x7ff) {
|
|
66
|
+
bytes += 2;
|
|
67
|
+
} else if (code >= 0xd800 && code <= 0xdbff && i + 1 < content.length) {
|
|
68
|
+
const next = content.charCodeAt(i + 1);
|
|
69
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
70
|
+
bytes += 4;
|
|
71
|
+
i++;
|
|
72
|
+
} else {
|
|
73
|
+
bytes += 3;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
bytes += 3;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return bytes;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function replaceUnpairedSurrogates(content: string): string {
|
|
83
|
+
let output = "";
|
|
84
|
+
for (let i = 0; i < content.length; i++) {
|
|
85
|
+
const code = content.charCodeAt(i);
|
|
86
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
87
|
+
if (i + 1 < content.length) {
|
|
88
|
+
const next = content.charCodeAt(i + 1);
|
|
89
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
90
|
+
output += content[i] + content[i + 1];
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
output += "�";
|
|
96
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
97
|
+
output += "�";
|
|
98
|
+
} else {
|
|
99
|
+
output += content[i];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return output;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Format bytes as human-readable size.
|
|
107
|
+
*/
|
|
108
|
+
export function formatSize(bytes: number): string {
|
|
109
|
+
if (bytes < 1024) {
|
|
110
|
+
return `${bytes}B`;
|
|
111
|
+
} else if (bytes < 1024 * 1024) {
|
|
112
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
113
|
+
} else {
|
|
114
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Truncate content from the head (keep first N lines/bytes).
|
|
120
|
+
* Suitable for file reads where you want to see the beginning.
|
|
121
|
+
*
|
|
122
|
+
* Never returns partial lines. If first line exceeds byte limit,
|
|
123
|
+
* returns empty content with firstLineExceedsLimit=true.
|
|
124
|
+
*/
|
|
125
|
+
export function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {
|
|
126
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
127
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
128
|
+
|
|
129
|
+
const totalBytes = utf8ByteLength(content);
|
|
130
|
+
const lines = content.split("\n");
|
|
131
|
+
const totalLines = lines.length;
|
|
132
|
+
|
|
133
|
+
// Check if no truncation needed
|
|
134
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
135
|
+
return {
|
|
136
|
+
content,
|
|
137
|
+
truncated: false,
|
|
138
|
+
truncatedBy: null,
|
|
139
|
+
totalLines,
|
|
140
|
+
totalBytes,
|
|
141
|
+
outputLines: totalLines,
|
|
142
|
+
outputBytes: totalBytes,
|
|
143
|
+
lastLinePartial: false,
|
|
144
|
+
firstLineExceedsLimit: false,
|
|
145
|
+
maxLines,
|
|
146
|
+
maxBytes,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Check if first line alone exceeds byte limit
|
|
151
|
+
const firstLineBytes = utf8ByteLength(lines[0]);
|
|
152
|
+
if (firstLineBytes > maxBytes) {
|
|
153
|
+
return {
|
|
154
|
+
content: "",
|
|
155
|
+
truncated: true,
|
|
156
|
+
truncatedBy: "bytes",
|
|
157
|
+
totalLines,
|
|
158
|
+
totalBytes,
|
|
159
|
+
outputLines: 0,
|
|
160
|
+
outputBytes: 0,
|
|
161
|
+
lastLinePartial: false,
|
|
162
|
+
firstLineExceedsLimit: true,
|
|
163
|
+
maxLines,
|
|
164
|
+
maxBytes,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Collect complete lines that fit
|
|
169
|
+
const outputLinesArr: string[] = [];
|
|
170
|
+
let outputBytesCount = 0;
|
|
171
|
+
let truncatedBy: "lines" | "bytes" = "lines";
|
|
172
|
+
|
|
173
|
+
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
174
|
+
const line = lines[i];
|
|
175
|
+
const lineBytes = utf8ByteLength(line) + (i > 0 ? 1 : 0); // +1 for newline
|
|
176
|
+
|
|
177
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
178
|
+
truncatedBy = "bytes";
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
outputLinesArr.push(line);
|
|
183
|
+
outputBytesCount += lineBytes;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If we exited due to line limit
|
|
187
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
188
|
+
truncatedBy = "lines";
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const outputContent = outputLinesArr.join("\n");
|
|
192
|
+
const finalOutputBytes = utf8ByteLength(outputContent);
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
content: outputContent,
|
|
196
|
+
truncated: true,
|
|
197
|
+
truncatedBy,
|
|
198
|
+
totalLines,
|
|
199
|
+
totalBytes,
|
|
200
|
+
outputLines: outputLinesArr.length,
|
|
201
|
+
outputBytes: finalOutputBytes,
|
|
202
|
+
lastLinePartial: false,
|
|
203
|
+
firstLineExceedsLimit: false,
|
|
204
|
+
maxLines,
|
|
205
|
+
maxBytes,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Truncate content from the tail (keep last N lines/bytes).
|
|
211
|
+
* Suitable for bash output where you want to see the end (errors, final results).
|
|
212
|
+
*
|
|
213
|
+
* May return partial first line if the last line of original content exceeds byte limit.
|
|
214
|
+
*/
|
|
215
|
+
export function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {
|
|
216
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
217
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
218
|
+
|
|
219
|
+
const totalBytes = utf8ByteLength(content);
|
|
220
|
+
const lines = content.split("\n");
|
|
221
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
222
|
+
const totalLines = lines.length;
|
|
223
|
+
|
|
224
|
+
// Check if no truncation needed
|
|
225
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
226
|
+
return {
|
|
227
|
+
content,
|
|
228
|
+
truncated: false,
|
|
229
|
+
truncatedBy: null,
|
|
230
|
+
totalLines,
|
|
231
|
+
totalBytes,
|
|
232
|
+
outputLines: totalLines,
|
|
233
|
+
outputBytes: totalBytes,
|
|
234
|
+
lastLinePartial: false,
|
|
235
|
+
firstLineExceedsLimit: false,
|
|
236
|
+
maxLines,
|
|
237
|
+
maxBytes,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Work backwards from the end
|
|
242
|
+
const outputLinesArr: string[] = [];
|
|
243
|
+
let outputBytesCount = 0;
|
|
244
|
+
let truncatedBy: "lines" | "bytes" = "lines";
|
|
245
|
+
let lastLinePartial = false;
|
|
246
|
+
|
|
247
|
+
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
248
|
+
const line = lines[i];
|
|
249
|
+
const lineBytes = utf8ByteLength(line) + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
|
|
250
|
+
|
|
251
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
252
|
+
truncatedBy = "bytes";
|
|
253
|
+
// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
|
|
254
|
+
// take the end of the line (partial)
|
|
255
|
+
if (outputLinesArr.length === 0) {
|
|
256
|
+
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
257
|
+
outputLinesArr.unshift(truncatedLine);
|
|
258
|
+
outputBytesCount = utf8ByteLength(truncatedLine);
|
|
259
|
+
lastLinePartial = true;
|
|
260
|
+
}
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
outputLinesArr.unshift(line);
|
|
265
|
+
outputBytesCount += lineBytes;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// If we exited due to line limit
|
|
269
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
270
|
+
truncatedBy = "lines";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const outputContent = outputLinesArr.join("\n");
|
|
274
|
+
const finalOutputBytes = utf8ByteLength(outputContent);
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
content: outputContent,
|
|
278
|
+
truncated: true,
|
|
279
|
+
truncatedBy,
|
|
280
|
+
totalLines,
|
|
281
|
+
totalBytes,
|
|
282
|
+
outputLines: outputLinesArr.length,
|
|
283
|
+
outputBytes: finalOutputBytes,
|
|
284
|
+
lastLinePartial,
|
|
285
|
+
firstLineExceedsLimit: false,
|
|
286
|
+
maxLines,
|
|
287
|
+
maxBytes,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Truncate a string to fit within a byte limit (from the end).
|
|
293
|
+
* Handles multi-byte UTF-8 characters correctly.
|
|
294
|
+
*/
|
|
295
|
+
function truncateStringToBytesFromEnd(str: string, maxBytes: number): string {
|
|
296
|
+
if (maxBytes <= 0) return "";
|
|
297
|
+
|
|
298
|
+
let outputBytes = 0;
|
|
299
|
+
let start = str.length;
|
|
300
|
+
let needsReplacement = false;
|
|
301
|
+
for (let i = str.length; i > 0; ) {
|
|
302
|
+
let characterStart = i - 1;
|
|
303
|
+
const code = str.charCodeAt(characterStart);
|
|
304
|
+
let characterBytes: number;
|
|
305
|
+
let unpairedSurrogate = false;
|
|
306
|
+
if (code >= 0xdc00 && code <= 0xdfff && characterStart > 0) {
|
|
307
|
+
const previous = str.charCodeAt(characterStart - 1);
|
|
308
|
+
if (previous >= 0xd800 && previous <= 0xdbff) {
|
|
309
|
+
characterStart--;
|
|
310
|
+
characterBytes = 4;
|
|
311
|
+
} else {
|
|
312
|
+
characterBytes = 3;
|
|
313
|
+
unpairedSurrogate = true;
|
|
314
|
+
}
|
|
315
|
+
} else if (code >= 0xd800 && code <= 0xdfff) {
|
|
316
|
+
characterBytes = 3;
|
|
317
|
+
unpairedSurrogate = true;
|
|
318
|
+
} else {
|
|
319
|
+
characterBytes = code <= 0x7f ? 1 : code <= 0x7ff ? 2 : 3;
|
|
320
|
+
}
|
|
321
|
+
if (outputBytes + characterBytes > maxBytes) break;
|
|
322
|
+
outputBytes += characterBytes;
|
|
323
|
+
start = characterStart;
|
|
324
|
+
needsReplacement ||= unpairedSurrogate;
|
|
325
|
+
i = characterStart;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const output = str.slice(start);
|
|
329
|
+
return needsReplacement ? replaceUnpairedSurrogates(output) : output;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Truncate a single line to max characters, adding [truncated] suffix.
|
|
334
|
+
* Used for grep match lines.
|
|
335
|
+
*/
|
|
336
|
+
export function truncateLine(
|
|
337
|
+
line: string,
|
|
338
|
+
maxChars: number = GREP_MAX_LINE_LENGTH,
|
|
339
|
+
): { text: string; wasTruncated: boolean } {
|
|
340
|
+
if (line.length <= maxChars) {
|
|
341
|
+
return { text: line, wasTruncated: false };
|
|
342
|
+
}
|
|
343
|
+
return { text: `${line.slice(0, maxChars)}... [truncated]`, wasTruncated: true };
|
|
344
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Core Agent
|
|
2
|
+
export * from "./agent.ts";
|
|
3
|
+
// Loop functions
|
|
4
|
+
export * from "./agent-loop.ts";
|
|
5
|
+
export * from "./harness/agent-harness.ts";
|
|
6
|
+
export {
|
|
7
|
+
type BranchPreparation,
|
|
8
|
+
type BranchSummaryDetails,
|
|
9
|
+
type CollectEntriesResult,
|
|
10
|
+
collectEntriesForBranchSummary,
|
|
11
|
+
generateBranchSummary,
|
|
12
|
+
prepareBranchEntries,
|
|
13
|
+
} from "./harness/compaction/branch-summarization.ts";
|
|
14
|
+
export {
|
|
15
|
+
calculateContextTokens,
|
|
16
|
+
compact,
|
|
17
|
+
DEFAULT_COMPACTION_SETTINGS,
|
|
18
|
+
estimateContextTokens,
|
|
19
|
+
estimateTokens,
|
|
20
|
+
findCutPoint,
|
|
21
|
+
findTurnStartIndex,
|
|
22
|
+
generateSummary,
|
|
23
|
+
getLastAssistantUsage,
|
|
24
|
+
prepareCompaction,
|
|
25
|
+
serializeConversation,
|
|
26
|
+
shouldCompact,
|
|
27
|
+
} from "./harness/compaction/compaction.ts";
|
|
28
|
+
export * from "./harness/messages.ts";
|
|
29
|
+
export * from "./harness/prompt-templates.ts";
|
|
30
|
+
export * from "./harness/session/jsonl-repo.ts";
|
|
31
|
+
export * from "./harness/session/memory-repo.ts";
|
|
32
|
+
export * from "./harness/session/repo-utils.ts";
|
|
33
|
+
export * from "./harness/session/session.ts";
|
|
34
|
+
export { uuidv7 } from "./harness/session/uuid.ts";
|
|
35
|
+
export * from "./harness/skills.ts";
|
|
36
|
+
export * from "./harness/system-prompt.ts";
|
|
37
|
+
// Harness
|
|
38
|
+
export * from "./harness/types.ts";
|
|
39
|
+
export * from "./harness/utils/shell-output.ts";
|
|
40
|
+
export * from "./harness/utils/truncate.ts";
|
|
41
|
+
// Proxy utilities
|
|
42
|
+
export * from "./proxy.ts";
|
|
43
|
+
// Types
|
|
44
|
+
export * from "./types.ts";
|