@giovannijecha/jecode 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/settings.js CHANGED
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
3
3
  import { chmod, mkdir } from "node:fs/promises";
4
4
  import * as path from "node:path";
5
5
  import { atomicWrite } from "./atomic.js";
6
+ import { MAX_COMPACTION_PERCENT, MIN_COMPACTION_PERCENT } from "./context/policy.js";
6
7
  import { EFFORTS } from "./effort.js";
7
8
  import { providerNames } from "./providers/index.js";
8
9
  import { parseOllamaEndpoint } from "./providers/ollama-endpoint.js";
@@ -59,6 +60,7 @@ function normalize(value) {
59
60
  const reducedMotion = typeof value["reducedMotion"] === "boolean" ? value["reducedMotion"] : undefined;
60
61
  const maxTokens = positiveInteger(value["maxTokens"]);
61
62
  const maxSteps = positiveInteger(value["maxSteps"]);
63
+ const compactionPercent = percentage(value["compactionPercent"]);
62
64
  return {
63
65
  ...(provider === undefined ? {} : { provider }),
64
66
  ...(models === undefined ? {} : { models }),
@@ -67,6 +69,7 @@ function normalize(value) {
67
69
  ...(reducedMotion === undefined ? {} : { reducedMotion }),
68
70
  ...(maxTokens === undefined ? {} : { maxTokens }),
69
71
  ...(maxSteps === undefined ? {} : { maxSteps }),
72
+ ...(compactionPercent === undefined ? {} : { compactionPercent }),
70
73
  };
71
74
  }
72
75
  function modelsOf(value, providers) {
@@ -91,6 +94,12 @@ function endpoint(value) {
91
94
  function positiveInteger(value) {
92
95
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
93
96
  }
97
+ function percentage(value) {
98
+ return typeof value === "number" && Number.isSafeInteger(value) &&
99
+ value >= MIN_COMPACTION_PERCENT && value <= MAX_COMPACTION_PERCENT
100
+ ? value
101
+ : undefined;
102
+ }
94
103
  function record(value) {
95
104
  return typeof value === "object" && value !== null && !Array.isArray(value);
96
105
  }
@@ -1,9 +1,12 @@
1
1
  // Foreground command and model-turn workflows for the TUI shell.
2
2
  import { handleCommand } from "../commands.js";
3
3
  import { runTurn } from "../controller.js";
4
+ import { resolveContextPolicy } from "../context/capacity.js";
5
+ import { compactContext } from "../context/compactor.js";
6
+ import { isContextOverflow, shouldResolveContextPolicy } from "../context/policy.js";
4
7
  import { updateSettings } from "../settings.js";
5
8
  import { saveTranscript } from "../transcript-export.js";
6
- import { recordUsage } from "../usage.js";
9
+ import { recordAuxiliaryUsage, recordUsage } from "../usage.js";
7
10
  import { answerAt } from "./approve.js";
8
11
  import { cancel as cancelOpen } from "./overlay.js";
9
12
  import { controllerOptions, turnFailure } from "./session-view.js";
@@ -81,12 +84,18 @@ export function appWorkflows(options) {
81
84
  return;
82
85
  const parentId = session.conversation.activeNodeId;
83
86
  const history = session.conversation.history;
87
+ const modelHistory = session.conversation.contextHistory;
84
88
  const historyStart = history.length;
85
89
  const blockStart = state.blocks.length;
86
90
  const createdAt = new Date().toISOString();
91
+ const prospectiveNodeId = session.conversation.nodes.length + 1;
87
92
  let nodeId;
93
+ let context;
94
+ let contextPolicy;
88
95
  options.emit({ kind: "user", text });
89
- history.push({ role: "user", content: [{ kind: "text", text }] });
96
+ const user = { role: "user", content: [{ kind: "text", text }] };
97
+ history.push(user);
98
+ modelHistory.push(structuredClone(user));
90
99
  const events = transcribe({
91
100
  emit: options.emit,
92
101
  render: options.render,
@@ -102,7 +111,7 @@ export function appWorkflows(options) {
102
111
  },
103
112
  usage: (usage) => recordUsage(session.usage, usage),
104
113
  });
105
- events.onCheckpoint = async (checkpoint, settlement) => {
114
+ const persist = async (checkpoint, settlement) => {
106
115
  const next = session.conversation.commit({
107
116
  ...(nodeId === undefined ? {} : { nodeId }),
108
117
  parentId,
@@ -114,14 +123,76 @@ export function appWorkflows(options) {
114
123
  },
115
124
  messages: checkpoint.slice(historyStart),
116
125
  blocks: state.blocks.slice(blockStart),
126
+ ...(context === undefined ? {} : { context }),
117
127
  }, settlement);
118
128
  await session.persistence?.checkpoint(next);
119
129
  session.conversation = next;
120
130
  nodeId = next.activeNodeId;
121
131
  };
132
+ const compact = async (checkpoint, projected, reason, error) => {
133
+ if (reason === "overflow" && (error === undefined || !isContextOverflow(error))) {
134
+ return undefined;
135
+ }
136
+ const force = reason === "overflow";
137
+ if (!shouldResolveContextPolicy(projected, session.usage.lastInputTokens, force)) {
138
+ return undefined;
139
+ }
140
+ if (contextPolicy === undefined) {
141
+ state.status = "Checking context";
142
+ options.render();
143
+ contextPolicy = resolveContextPolicy({
144
+ provider: session.provider,
145
+ model: session.model,
146
+ compactionPercent: session.config.compactionPercent,
147
+ signal: activity.control.signal,
148
+ onStatus: (status) => {
149
+ state.status = status;
150
+ options.render();
151
+ },
152
+ }).finally(() => {
153
+ state.status = WAITING;
154
+ options.render();
155
+ });
156
+ }
157
+ const result = await compactContext({
158
+ provider: session.provider,
159
+ model: session.model,
160
+ effort: session.config.effort,
161
+ context: projected,
162
+ turn: checkpoint.slice(historyStart),
163
+ nodeId: nodeId ?? prospectiveNodeId,
164
+ coveredMessages: context?.messageCount ?? 0,
165
+ lastInputTokens: session.usage.lastInputTokens,
166
+ signal: activity.control.signal,
167
+ force,
168
+ policy: await contextPolicy,
169
+ onBegin: () => {
170
+ state.status = "Compacting";
171
+ options.render();
172
+ },
173
+ onEnd: () => {
174
+ state.status = WAITING;
175
+ options.render();
176
+ },
177
+ });
178
+ if (result === undefined)
179
+ return undefined;
180
+ context = result.anchor;
181
+ if (result.usage !== undefined)
182
+ recordAuxiliaryUsage(session.usage, result.usage);
183
+ return result.messages;
184
+ };
185
+ events.onContext = compact;
186
+ events.onCheckpoint = async (checkpoint, settlement, projected) => {
187
+ await persist(checkpoint, settlement);
188
+ const compacted = await compact(checkpoint, projected, "budget");
189
+ if (compacted !== undefined)
190
+ await persist(checkpoint, settlement);
191
+ return compacted;
192
+ };
122
193
  let finishReason;
123
194
  try {
124
- await runTurn(history, controllerOptions(session, permissions.availableTools()), events, activity.control.signal);
195
+ await runTurn(history, controllerOptions(session, permissions.availableTools()), events, activity.control.signal, modelHistory);
125
196
  }
126
197
  catch (error) {
127
198
  const interrupted = activity.control.signal.aborted;
package/dist/usage.js CHANGED
@@ -13,6 +13,14 @@ export function emptyUsage() {
13
13
  export function recordUsage(total, next) {
14
14
  total.requests += 1;
15
15
  total.lastInputTokens = next.inputTokens;
16
+ addUsage(total, next);
17
+ }
18
+ /** Account for an internal request without replacing the main context signal. */
19
+ export function recordAuxiliaryUsage(total, next) {
20
+ total.requests += 1;
21
+ addUsage(total, next);
22
+ }
23
+ function addUsage(total, next) {
16
24
  total.inputTokens += next.inputTokens;
17
25
  total.outputTokens += next.outputTokens;
18
26
  total.cachedInputTokens += next.cachedInputTokens;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giovannijecha/jecode",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "An owned coding agent with zero external runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "repository": {