@giovannijecha/jecode 0.8.6 → 0.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -8
- package/assets/tokenizers/LICENSE +21 -0
- package/assets/tokenizers/o200k-base.tiktoken.gz +0 -0
- package/dist/cli-info.js +8 -13
- package/dist/commands.js +7 -3
- package/dist/config.js +33 -47
- package/dist/context/automatic.js +16 -1
- package/dist/context/budget.js +5 -2
- package/dist/context/compactor.js +91 -37
- package/dist/context/diagnostics.js +108 -0
- package/dist/context/lifetime.js +18 -0
- package/dist/context/manager.js +67 -0
- package/dist/context/manual.js +11 -10
- package/dist/context/measurement.js +75 -0
- package/dist/context/policy.js +13 -10
- package/dist/context/request-observation.js +46 -0
- package/dist/context/request-projection.js +8 -34
- package/dist/context/request.js +22 -0
- package/dist/context/tokenizer/bpe.js +68 -0
- package/dist/context/tokenizer/o200k.js +54 -0
- package/dist/context/tokenizer/vocabulary.js +34 -0
- package/dist/controller-request.js +47 -45
- package/dist/controller.js +13 -2
- package/dist/input-boundary.js +0 -62
- package/dist/launch.js +13 -9
- package/dist/model-command.js +7 -17
- package/dist/oauth-result-page.js +68 -0
- package/dist/openai-oauth-callback.js +2 -50
- package/dist/permission-command.js +9 -24
- package/dist/permissions.js +10 -8
- package/dist/providers/anthropic-wire.js +1 -1
- package/dist/providers/anthropic.js +3 -1
- package/dist/providers/input-measurement.js +85 -0
- package/dist/providers/ollama-wire.js +1 -1
- package/dist/providers/ollama.js +2 -0
- package/dist/providers/openai-codex.js +3 -0
- package/dist/providers/openai-stream.js +20 -8
- package/dist/providers/openai-summary.js +37 -0
- package/dist/providers/openai-wire.js +1 -1
- package/dist/providers/openai.js +3 -0
- package/dist/settings-command.js +5 -10
- package/dist/settings.js +1 -1
- package/dist/start.js +35 -51
- package/dist/timeline.js +2 -0
- package/dist/tui/activity.js +1 -1
- package/dist/tui/app-input.js +34 -17
- package/dist/tui/app-workflows.js +7 -2
- package/dist/tui/app.js +55 -28
- package/dist/tui/approve.js +3 -4
- package/dist/tui/blocks.js +5 -7
- package/dist/tui/command-workflow.js +14 -8
- package/dist/tui/components/command-menu.js +1 -1
- package/dist/tui/components/menu.js +19 -11
- package/dist/tui/components/status.js +1 -1
- package/dist/tui/frame.js +8 -3
- package/dist/tui/help.js +2 -1
- package/dist/tui/picker-layout.js +5 -1
- package/dist/tui/screen.js +7 -0
- package/dist/tui/session-view.js +0 -1
- package/dist/tui/transcript-grammar.js +1 -8
- package/dist/tui/transcript-view.js +4 -0
- package/dist/tui/turn-workflow.js +47 -76
- package/dist/tui/view.js +4 -4
- package/dist/ui/render.js +0 -4
- package/package.json +5 -1
- package/dist/batch-view.js +0 -42
- package/dist/batch.js +0 -269
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// One model turn, including steering, compaction, durable settlement, and recovery.
|
|
2
2
|
import { runTurn } from "../controller.js";
|
|
3
3
|
import { resolveContextPolicy } from "../context/capacity.js";
|
|
4
|
-
import {
|
|
5
|
-
import { compactContext } from "../context/compactor.js";
|
|
6
|
-
import { isContextOverflow } from "../context/policy.js";
|
|
4
|
+
import { contextManager } from "../context/manager.js";
|
|
7
5
|
import { steeringInbox } from "../steering.js";
|
|
8
6
|
import { recordAuxiliaryUsage, recordRequestInput, recordUsage } from "../usage.js";
|
|
9
7
|
import { toolSpecs } from "../tools/index.js";
|
|
@@ -11,16 +9,21 @@ import { requestIdentityForSession } from "../request-identity.js";
|
|
|
11
9
|
import { transition } from "./activity.js";
|
|
12
10
|
import { answerAt } from "./approve.js";
|
|
13
11
|
import * as edit from "./editor.js";
|
|
14
|
-
import { controllerOptions, turnFailure } from "./session-view.js";
|
|
12
|
+
import { controllerOptions, footerInfo, turnFailure } from "./session-view.js";
|
|
15
13
|
import { transcribe } from "./turn.js";
|
|
16
14
|
const WAITING = "Waiting";
|
|
17
|
-
export function turnWorkflow(options, automaticCompaction) {
|
|
15
|
+
export function turnWorkflow(options, automaticCompaction, inputs) {
|
|
18
16
|
const { session, state, permissions, feedback } = options;
|
|
19
17
|
let activeSteering;
|
|
20
18
|
async function turn(text) {
|
|
21
19
|
const activity = options.startActivity("turn", WAITING);
|
|
22
20
|
if (activity === undefined)
|
|
23
21
|
return;
|
|
22
|
+
// Settings menus remain live, but every request, compaction, and checkpoint
|
|
23
|
+
// in this turn must retain the selection with which the turn started.
|
|
24
|
+
const runtime = { ...session, config: { ...session.config } };
|
|
25
|
+
const requestIdentity = requestIdentityForSession(session);
|
|
26
|
+
state.turnFooter = footerInfo(runtime);
|
|
24
27
|
const inbox = steeringInbox((pending, accepting) => {
|
|
25
28
|
if (activeSteering !== inbox)
|
|
26
29
|
return;
|
|
@@ -51,9 +54,9 @@ export function turnWorkflow(options, automaticCompaction) {
|
|
|
51
54
|
options.render();
|
|
52
55
|
}
|
|
53
56
|
return resolveContextPolicy({
|
|
54
|
-
provider:
|
|
55
|
-
model:
|
|
56
|
-
compactionPercent:
|
|
57
|
+
provider: runtime.provider,
|
|
58
|
+
model: runtime.model,
|
|
59
|
+
compactionPercent: runtime.config.compactionPercent,
|
|
57
60
|
signal: activity.control.signal,
|
|
58
61
|
onStatus: (said) => {
|
|
59
62
|
visible = true;
|
|
@@ -67,6 +70,11 @@ export function turnWorkflow(options, automaticCompaction) {
|
|
|
67
70
|
}
|
|
68
71
|
});
|
|
69
72
|
};
|
|
73
|
+
const requestOptions = {
|
|
74
|
+
...controllerOptions(session, policy, turnTools, inbox),
|
|
75
|
+
inputMeter: inputs.forTurn(runtime.provider, requestIdentity.conversationId),
|
|
76
|
+
toolAllowed: (call) => permissions.allowed(call),
|
|
77
|
+
};
|
|
70
78
|
options.emit({ kind: "user", text });
|
|
71
79
|
const user = { role: "user", content: [{ kind: "text", text }] };
|
|
72
80
|
history.push(user);
|
|
@@ -78,7 +86,7 @@ export function turnWorkflow(options, automaticCompaction) {
|
|
|
78
86
|
approved: (call) => permissions.approved(call),
|
|
79
87
|
remember: (call) => permissions.remember(call),
|
|
80
88
|
ask: (prompt, settle) => {
|
|
81
|
-
state.
|
|
89
|
+
state.approval = { picker: prompt, settle: (index) => settle(answerAt(index)) };
|
|
82
90
|
options.render();
|
|
83
91
|
},
|
|
84
92
|
status: (text) => {
|
|
@@ -93,9 +101,9 @@ export function turnWorkflow(options, automaticCompaction) {
|
|
|
93
101
|
parentId,
|
|
94
102
|
createdAt,
|
|
95
103
|
identity: {
|
|
96
|
-
providerId:
|
|
97
|
-
model:
|
|
98
|
-
effort:
|
|
104
|
+
providerId: runtime.provider.id,
|
|
105
|
+
model: runtime.model,
|
|
106
|
+
effort: runtime.config.effort,
|
|
99
107
|
},
|
|
100
108
|
messages: checkpoint.slice(historyStart),
|
|
101
109
|
blocks: state.blocks.slice(blockStart),
|
|
@@ -108,86 +116,49 @@ export function turnWorkflow(options, automaticCompaction) {
|
|
|
108
116
|
state.committedNodeId = next.activeNodeId;
|
|
109
117
|
unpersistedSteering.length = 0;
|
|
110
118
|
};
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
force,
|
|
134
|
-
policy: request.policy,
|
|
135
|
-
requestEnvelope: {
|
|
136
|
-
system: session.system,
|
|
137
|
-
tools: specs,
|
|
138
|
-
maxOutputTokens: session.config.maxTokens,
|
|
139
|
-
},
|
|
140
|
-
requestIdentity: requestIdentityForSession(session),
|
|
141
|
-
onBegin: () => {
|
|
142
|
-
attempted = true;
|
|
143
|
-
status("Compacting");
|
|
144
|
-
options.render();
|
|
145
|
-
},
|
|
146
|
-
onEnd: () => {
|
|
147
|
-
status(WAITING);
|
|
148
|
-
options.render();
|
|
149
|
-
},
|
|
150
|
-
});
|
|
151
|
-
if (result === undefined) {
|
|
152
|
-
if (attempted && !activity.control.signal.aborted)
|
|
153
|
-
automaticCompaction.failed(attempt);
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
automaticCompaction.succeeded(attempt);
|
|
157
|
-
context = result.anchor;
|
|
158
|
-
if (result.usage !== undefined)
|
|
159
|
-
recordAuxiliaryUsage(session.usage, result.usage);
|
|
160
|
-
return result.messages;
|
|
119
|
+
const manager = contextManager({
|
|
120
|
+
provider: runtime.provider,
|
|
121
|
+
model: runtime.model,
|
|
122
|
+
effort: runtime.config.effort,
|
|
123
|
+
system: runtime.system,
|
|
124
|
+
tools: specs,
|
|
125
|
+
maxOutputTokens: runtime.config.maxTokens,
|
|
126
|
+
historyStart,
|
|
127
|
+
nodeId: () => nodeId ?? prospectiveNodeId,
|
|
128
|
+
gate: automaticCompaction,
|
|
129
|
+
identity: requestIdentity,
|
|
130
|
+
signal: activity.control.signal,
|
|
131
|
+
onStatus(active) {
|
|
132
|
+
status(active ? "Compacting" : WAITING);
|
|
133
|
+
options.render();
|
|
134
|
+
},
|
|
135
|
+
onUsage: (usage) => recordAuxiliaryUsage(session.usage, usage),
|
|
136
|
+
});
|
|
137
|
+
events.onContext = async (checkpoint, projected, request) => {
|
|
138
|
+
const result = await manager.compact(checkpoint, projected, request);
|
|
139
|
+
context = manager.anchor;
|
|
140
|
+
return result;
|
|
161
141
|
};
|
|
162
|
-
events.onContext = compact;
|
|
163
142
|
events.onSteering = (guidance) => {
|
|
164
143
|
unpersistedSteering.push(guidance);
|
|
165
144
|
options.emit({ kind: "user", text: guidance });
|
|
166
145
|
options.render();
|
|
167
146
|
};
|
|
168
|
-
events.onCheckpoint = async (checkpoint, settlement
|
|
147
|
+
events.onCheckpoint = async (checkpoint, settlement) => {
|
|
169
148
|
await persist(checkpoint, settlement);
|
|
170
|
-
const compacted = await compact(checkpoint, projected, {
|
|
171
|
-
reason: "budget",
|
|
172
|
-
policy: await policy(),
|
|
173
|
-
inputTokens: session.usage.lastInputTokens,
|
|
174
|
-
projectionSaturated: false,
|
|
175
|
-
});
|
|
176
|
-
if (compacted !== undefined)
|
|
177
|
-
await persist(checkpoint, settlement);
|
|
178
|
-
return compacted;
|
|
179
149
|
};
|
|
180
150
|
let finishReason;
|
|
181
151
|
let failed;
|
|
182
152
|
try {
|
|
183
|
-
await runTurn(history,
|
|
153
|
+
await runTurn(history, requestOptions, events, activity.control.signal, modelHistory);
|
|
184
154
|
}
|
|
185
155
|
catch (error) {
|
|
156
|
+
inputs.reset();
|
|
186
157
|
const interrupted = activity.control.signal.aborted;
|
|
187
158
|
const completed = nodeId !== undefined && session.conversation.activeNodeId === nodeId &&
|
|
188
159
|
session.conversation.activeNode?.settlement === "completed";
|
|
189
160
|
if (completed) {
|
|
190
|
-
const notice = turnFailure(
|
|
161
|
+
const notice = turnFailure(runtime, error, interrupted);
|
|
191
162
|
feedback.show({ text: notice.text, tone: notice.tone, timeoutMs: 6_000 });
|
|
192
163
|
}
|
|
193
164
|
else {
|
|
@@ -203,7 +174,7 @@ export function turnWorkflow(options, automaticCompaction) {
|
|
|
203
174
|
try {
|
|
204
175
|
events.finish(finishReason);
|
|
205
176
|
if (failed !== undefined) {
|
|
206
|
-
const notice = turnFailure(
|
|
177
|
+
const notice = turnFailure(runtime, failed.error, failed.interrupted);
|
|
207
178
|
const settlement = failed.interrupted ? "interrupted" : "failed";
|
|
208
179
|
const failure = {
|
|
209
180
|
text: notice.text,
|
package/dist/tui/view.js
CHANGED
|
@@ -39,10 +39,10 @@ export function compose(view, size, transcript = transcriptRenderer()) {
|
|
|
39
39
|
}
|
|
40
40
|
function dockRows(view, width, height) {
|
|
41
41
|
const status = renderStatus({
|
|
42
|
-
// A modal
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// A modal names its command; background model work still needs visible
|
|
43
|
+
// status. Its interruption/steering keys are suspended while a menu owns input.
|
|
44
|
+
status: view.modal === undefined || view.turnActive ? view.status : undefined,
|
|
45
|
+
interruptible: view.modal === undefined,
|
|
46
46
|
steering: view.modal === undefined ? view.steering : undefined,
|
|
47
47
|
feedback: view.feedback,
|
|
48
48
|
readiness: view.readiness,
|
package/dist/ui/render.js
CHANGED
|
@@ -45,10 +45,6 @@ export function paint(seg) {
|
|
|
45
45
|
export function plainLen(segs) {
|
|
46
46
|
return segs.reduce((n, seg) => n + textWidth(terminalText(seg.text)), 0);
|
|
47
47
|
}
|
|
48
|
-
export function columns() {
|
|
49
|
-
const width = process.stdout.columns;
|
|
50
|
-
return typeof width === "number" && width >= 40 ? width : 80;
|
|
51
|
-
}
|
|
52
48
|
/**
|
|
53
49
|
* The margin every row keeps from the edges of the terminal: none.
|
|
54
50
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@giovannijecha/jecode",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "An owned coding agent with zero external runtime dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
"dist/",
|
|
28
28
|
"assets/jeco-256.png",
|
|
29
29
|
"assets/wordmark-steel.svg",
|
|
30
|
+
"assets/tokenizers/o200k-base.tiktoken.gz",
|
|
31
|
+
"assets/tokenizers/LICENSE",
|
|
30
32
|
"LICENSE",
|
|
31
33
|
"README.md"
|
|
32
34
|
],
|
|
@@ -45,11 +47,13 @@
|
|
|
45
47
|
"pack:release": "npm run build:release && npm pack --ignore-scripts",
|
|
46
48
|
"start": "node src/main.ts",
|
|
47
49
|
"tui:lab": "node dev/tui/main.ts",
|
|
50
|
+
"web:preview": "node dev/web/main.ts",
|
|
48
51
|
"bench:context": "node dev/benchmarks/context.ts",
|
|
49
52
|
"bench:redaction": "node dev/benchmarks/redaction.ts",
|
|
50
53
|
"bench:search": "node dev/benchmarks/search.ts",
|
|
51
54
|
"bench:session": "node dev/benchmarks/session.ts",
|
|
52
55
|
"bench:transcript": "node dev/benchmarks/transcript.ts",
|
|
56
|
+
"bench:tui": "node --expose-gc dev/benchmarks/tui.ts",
|
|
53
57
|
"typecheck": "tsc --noEmit",
|
|
54
58
|
"test": "npm run build:release && node --test",
|
|
55
59
|
"coverage": "npm run build:release && node --test --experimental-test-coverage --test-coverage-include=\"src/**/*.ts\" --test-coverage-lines=80 --test-coverage-branches=75 --test-coverage-functions=75",
|
package/dist/batch-view.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// Project semantic blocks into a compact transcript for pipes.
|
|
2
|
-
import { renderAnswer, renderReasoning, renderUser } from "./tui/components/messages.js";
|
|
3
|
-
import { renderNotice } from "./tui/components/misc.js";
|
|
4
|
-
import { renderDetail } from "./tui/components/tool-evidence.js";
|
|
5
|
-
import { stateInk, stateMark } from "./tui/components/tool-motion.js";
|
|
6
|
-
import { toolDuration } from "./duration.js";
|
|
7
|
-
import { row } from "./ui/render.js";
|
|
8
|
-
export function renderBatch(block, width, pal) {
|
|
9
|
-
const rows = [];
|
|
10
|
-
let lastWasBlank = false;
|
|
11
|
-
for (const rendered of batchRows(block, width, pal)) {
|
|
12
|
-
const line = rendered.trimEnd();
|
|
13
|
-
const blank = line === "";
|
|
14
|
-
if (blank && lastWasBlank)
|
|
15
|
-
continue;
|
|
16
|
-
rows.push(line);
|
|
17
|
-
lastWasBlank = blank;
|
|
18
|
-
}
|
|
19
|
-
while (rows.at(-1) === "")
|
|
20
|
-
rows.pop();
|
|
21
|
-
return rows;
|
|
22
|
-
}
|
|
23
|
-
function batchRows(block, width, pal) {
|
|
24
|
-
switch (block.kind) {
|
|
25
|
-
case "answer": return renderAnswer(block, width, pal);
|
|
26
|
-
case "reasoning": return renderReasoning(block, width, pal);
|
|
27
|
-
case "user": return renderUser(block, width, pal);
|
|
28
|
-
case "notice": return renderNotice(block, width, pal);
|
|
29
|
-
case "tool": {
|
|
30
|
-
// Pipes cannot expand records or repaint a running connector.
|
|
31
|
-
const mark = stateMark(block);
|
|
32
|
-
const result = [block.right, block.durationMs === undefined ? "" : toolDuration(block.durationMs)]
|
|
33
|
-
.filter(Boolean).join(" · ");
|
|
34
|
-
return ["", row(width, [
|
|
35
|
-
{ text: `${mark} `, fg: stateInk(block, pal) },
|
|
36
|
-
{ text: block.name, fg: pal.ink.bright },
|
|
37
|
-
{ text: block.target === "" ? "" : ` ${block.target}`, fg: pal.technical },
|
|
38
|
-
], [{ text: result, fg: pal.ink.muted }]),
|
|
39
|
-
...(block.body ?? []).map((detail) => renderDetail(detail, block.tone, width, pal))];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
package/dist/batch.js
DELETED
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
// The non-interactive path: stdin is a pipe, so there is no screen to own.
|
|
2
|
-
//
|
|
3
|
-
// It exists so the agent can be scripted and tested. It shares the controller,
|
|
4
|
-
// the tools and the block renderer with the TUI — only the surface differs.
|
|
5
|
-
import { stdin, stdout } from "node:process";
|
|
6
|
-
import { runTurn } from "./controller.js";
|
|
7
|
-
import { resolveContextPolicy } from "./context/capacity.js";
|
|
8
|
-
import { automaticCompactionGate, automaticCompactionKey, } from "./context/automatic.js";
|
|
9
|
-
import { compactContext } from "./context/compactor.js";
|
|
10
|
-
import { compactSession } from "./context/manual.js";
|
|
11
|
-
import { isContextOverflow } from "./context/policy.js";
|
|
12
|
-
import { handleCommand } from "./commands.js";
|
|
13
|
-
import { renderBatch } from "./batch-view.js";
|
|
14
|
-
import { columns } from "./ui/render.js";
|
|
15
|
-
import { terminalText } from "./ui/terminal-text.js";
|
|
16
|
-
import { recordAuxiliaryUsage, recordRequestInput, recordUsage } from "./usage.js";
|
|
17
|
-
import { assertPromptLength, boundedInputLines } from "./input-boundary.js";
|
|
18
|
-
import { providerFailure } from "./provider-errors.js";
|
|
19
|
-
import { toolSpecs } from "./tools/index.js";
|
|
20
|
-
import { requestIdentityForSession } from "./request-identity.js";
|
|
21
|
-
export async function runBatch(session, environment = {}) {
|
|
22
|
-
const write = environment.write ?? ((text) => stdout.write(text));
|
|
23
|
-
const width = environment.width ?? columns();
|
|
24
|
-
const signal = environment.signal;
|
|
25
|
-
const source = environment.lines ?? boundedInputLines(stdin);
|
|
26
|
-
const lines = abortableLines(source, signal);
|
|
27
|
-
const automaticCompaction = automaticCompactionGate();
|
|
28
|
-
const emit = (block) => {
|
|
29
|
-
for (const line of renderBatch(block, width, session.palette))
|
|
30
|
-
write(`${line}\n`);
|
|
31
|
-
};
|
|
32
|
-
try {
|
|
33
|
-
throwIfAborted(signal);
|
|
34
|
-
for await (const raw of lines) {
|
|
35
|
-
throwIfAborted(signal);
|
|
36
|
-
assertPromptLength(raw.length);
|
|
37
|
-
const line = raw.trim();
|
|
38
|
-
if (line === "")
|
|
39
|
-
continue;
|
|
40
|
-
if (line.startsWith("/")) {
|
|
41
|
-
if ((await handleCommand(line, session, {
|
|
42
|
-
emit,
|
|
43
|
-
signal,
|
|
44
|
-
reset: () => {
|
|
45
|
-
automaticCompaction.reset();
|
|
46
|
-
},
|
|
47
|
-
compact: async () => {
|
|
48
|
-
const result = await compactSession(session, { signal });
|
|
49
|
-
if (result === "compacted")
|
|
50
|
-
automaticCompaction.reset();
|
|
51
|
-
return result;
|
|
52
|
-
},
|
|
53
|
-
})) === "exit")
|
|
54
|
-
break;
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
write(`> ${terminalText(line)}\n`);
|
|
58
|
-
const parentId = session.conversation.activeNodeId;
|
|
59
|
-
const createdAt = new Date().toISOString();
|
|
60
|
-
const history = session.conversation.history;
|
|
61
|
-
const modelHistory = session.conversation.contextHistory;
|
|
62
|
-
const before = history.length;
|
|
63
|
-
const prospectiveNodeId = session.conversation.nodes.length + 1;
|
|
64
|
-
let nodeId;
|
|
65
|
-
let context;
|
|
66
|
-
const policy = () => {
|
|
67
|
-
return resolveContextPolicy({
|
|
68
|
-
provider: session.provider,
|
|
69
|
-
model: session.model,
|
|
70
|
-
compactionPercent: session.config.compactionPercent,
|
|
71
|
-
signal,
|
|
72
|
-
});
|
|
73
|
-
};
|
|
74
|
-
const user = { role: "user", content: [{ kind: "text", text: line }] };
|
|
75
|
-
history.push(user);
|
|
76
|
-
modelHistory.push(structuredClone(user));
|
|
77
|
-
const turn = events(emit, session);
|
|
78
|
-
const specs = toolSpecs(session.tools);
|
|
79
|
-
const commit = (checkpoint, settlement) => {
|
|
80
|
-
session.conversation = session.conversation.commit({
|
|
81
|
-
...(nodeId === undefined ? {} : { nodeId }),
|
|
82
|
-
parentId,
|
|
83
|
-
createdAt,
|
|
84
|
-
identity: {
|
|
85
|
-
providerId: session.provider.id,
|
|
86
|
-
model: session.model,
|
|
87
|
-
effort: session.config.effort,
|
|
88
|
-
},
|
|
89
|
-
messages: checkpoint.slice(before),
|
|
90
|
-
blocks: [],
|
|
91
|
-
...(context === undefined ? {} : { context }),
|
|
92
|
-
}, settlement);
|
|
93
|
-
nodeId = session.conversation.activeNodeId;
|
|
94
|
-
};
|
|
95
|
-
const compact = async (checkpoint, projected, request) => {
|
|
96
|
-
if (request.reason === "overflow" &&
|
|
97
|
-
(request.error === undefined || !isContextOverflow(request.error))) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
const force = request.reason === "overflow" || request.projectionSaturated;
|
|
101
|
-
const key = automaticCompactionKey(session.provider.id, session.model, nodeId ?? prospectiveNodeId, checkpoint.length);
|
|
102
|
-
const attempt = { key, reason: request.reason };
|
|
103
|
-
if (!automaticCompaction.allows(attempt))
|
|
104
|
-
return undefined;
|
|
105
|
-
let attempted = false;
|
|
106
|
-
const result = await compactContext({
|
|
107
|
-
provider: session.provider,
|
|
108
|
-
model: session.model,
|
|
109
|
-
effort: session.config.effort,
|
|
110
|
-
signal,
|
|
111
|
-
context: projected,
|
|
112
|
-
turn: checkpoint.slice(before),
|
|
113
|
-
nodeId: nodeId ?? prospectiveNodeId,
|
|
114
|
-
coveredMessages: context?.messageCount ?? 0,
|
|
115
|
-
lastInputTokens: Math.max(session.usage.lastInputTokens, request.inputTokens),
|
|
116
|
-
estimatedInputTokens: request.inputTokens,
|
|
117
|
-
force,
|
|
118
|
-
policy: request.policy,
|
|
119
|
-
requestEnvelope: {
|
|
120
|
-
system: session.system,
|
|
121
|
-
tools: specs,
|
|
122
|
-
maxOutputTokens: session.config.maxTokens,
|
|
123
|
-
},
|
|
124
|
-
requestIdentity: requestIdentityForSession(session),
|
|
125
|
-
onBegin: () => {
|
|
126
|
-
attempted = true;
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
if (result === undefined) {
|
|
130
|
-
if (attempted && signal?.aborted !== true)
|
|
131
|
-
automaticCompaction.failed(attempt);
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
automaticCompaction.succeeded(attempt);
|
|
135
|
-
context = result.anchor;
|
|
136
|
-
if (result.usage !== undefined)
|
|
137
|
-
recordAuxiliaryUsage(session.usage, result.usage);
|
|
138
|
-
return result.messages;
|
|
139
|
-
};
|
|
140
|
-
turn.onContext = compact;
|
|
141
|
-
turn.onCheckpoint = async (checkpoint, settlement, projected) => {
|
|
142
|
-
commit(checkpoint, settlement);
|
|
143
|
-
const compacted = await compact(checkpoint, projected, {
|
|
144
|
-
reason: "budget",
|
|
145
|
-
policy: await policy(),
|
|
146
|
-
inputTokens: session.usage.lastInputTokens,
|
|
147
|
-
projectionSaturated: false,
|
|
148
|
-
});
|
|
149
|
-
if (compacted !== undefined)
|
|
150
|
-
commit(checkpoint, settlement);
|
|
151
|
-
return compacted;
|
|
152
|
-
};
|
|
153
|
-
try {
|
|
154
|
-
await runTurn(history, options(session, policy), turn, signal, modelHistory);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
throwIfAborted(signal);
|
|
158
|
-
if (!(error instanceof Error))
|
|
159
|
-
throw error;
|
|
160
|
-
const message = providerFailure(session.provider, error, true);
|
|
161
|
-
if (message === error.message)
|
|
162
|
-
throw error;
|
|
163
|
-
throw new Error(message, { cause: error });
|
|
164
|
-
}
|
|
165
|
-
turn.flush();
|
|
166
|
-
}
|
|
167
|
-
throwIfAborted(signal);
|
|
168
|
-
}
|
|
169
|
-
finally {
|
|
170
|
-
if (environment.lines === undefined)
|
|
171
|
-
stdin.pause();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
function throwIfAborted(signal) {
|
|
175
|
-
if (signal?.aborted === true)
|
|
176
|
-
throw signal.reason;
|
|
177
|
-
}
|
|
178
|
-
async function* abortableLines(source, signal) {
|
|
179
|
-
const iterator = source[Symbol.asyncIterator]();
|
|
180
|
-
let exhausted = false;
|
|
181
|
-
try {
|
|
182
|
-
while (true) {
|
|
183
|
-
throwIfAborted(signal);
|
|
184
|
-
const next = signal === undefined
|
|
185
|
-
? await iterator.next()
|
|
186
|
-
: await new Promise((resolve, reject) => {
|
|
187
|
-
const abort = () => reject(signal.reason);
|
|
188
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
189
|
-
iterator.next().then((result) => {
|
|
190
|
-
signal.removeEventListener("abort", abort);
|
|
191
|
-
resolve(result);
|
|
192
|
-
}, (error) => {
|
|
193
|
-
signal.removeEventListener("abort", abort);
|
|
194
|
-
reject(error);
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
if (next.done === true) {
|
|
198
|
-
exhausted = true;
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
yield next.value;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
finally {
|
|
205
|
-
if (!exhausted && iterator.return !== undefined) {
|
|
206
|
-
const closing = iterator.return();
|
|
207
|
-
if (signal?.aborted === true)
|
|
208
|
-
void closing.catch(() => { });
|
|
209
|
-
else
|
|
210
|
-
await closing;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function options(session, contextPolicy) {
|
|
215
|
-
return {
|
|
216
|
-
provider: session.provider,
|
|
217
|
-
tools: session.tools,
|
|
218
|
-
model: session.model,
|
|
219
|
-
system: session.system,
|
|
220
|
-
maxTokens: session.config.maxTokens,
|
|
221
|
-
contextPolicy,
|
|
222
|
-
effort: session.config.effort,
|
|
223
|
-
requestIdentity: requestIdentityForSession(session),
|
|
224
|
-
maxModelRequests: session.config.maxModelRequests,
|
|
225
|
-
toolContext: { root: session.config.root },
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
// Prose is buffered rather than streamed: without a screen to repaint there is
|
|
229
|
-
// nothing to gain from partial lines, and plenty to lose in readability.
|
|
230
|
-
function events(emit, session) {
|
|
231
|
-
let answer = "";
|
|
232
|
-
const flush = () => {
|
|
233
|
-
if (answer === "")
|
|
234
|
-
return;
|
|
235
|
-
emit({ kind: "answer", text: answer });
|
|
236
|
-
answer = "";
|
|
237
|
-
};
|
|
238
|
-
return {
|
|
239
|
-
flush,
|
|
240
|
-
onStream(event) {
|
|
241
|
-
if (event.kind === "text")
|
|
242
|
-
answer += event.text;
|
|
243
|
-
},
|
|
244
|
-
onToolCall() {
|
|
245
|
-
flush();
|
|
246
|
-
},
|
|
247
|
-
onToolResult(call, result, summary) {
|
|
248
|
-
emit({
|
|
249
|
-
kind: "tool",
|
|
250
|
-
name: call.name,
|
|
251
|
-
target: "",
|
|
252
|
-
right: summary ?? "",
|
|
253
|
-
tone: result.isError ? "fail" : "ok",
|
|
254
|
-
});
|
|
255
|
-
},
|
|
256
|
-
approve() {
|
|
257
|
-
flush();
|
|
258
|
-
// Nobody is watching a pipe. Approval has to be granted up front with
|
|
259
|
-
// --auto-approve, never inferred from silence.
|
|
260
|
-
return Promise.resolve(session.config.autoApprove);
|
|
261
|
-
},
|
|
262
|
-
onUsage(usage) {
|
|
263
|
-
recordUsage(session.usage, usage);
|
|
264
|
-
},
|
|
265
|
-
onRequestInput(inputTokens) {
|
|
266
|
-
recordRequestInput(session.usage, inputTokens);
|
|
267
|
-
},
|
|
268
|
-
};
|
|
269
|
-
}
|