@glbgpt/cli 0.2.3 → 0.2.5

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.
Files changed (74) hide show
  1. package/README.md +50 -4
  2. package/dist/src/coding/bashTool.js +27 -11
  3. package/dist/src/coding/bashTool.js.map +1 -1
  4. package/dist/src/coding/deliveryCheck.d.ts +7 -0
  5. package/dist/src/coding/deliveryCheck.js +143 -0
  6. package/dist/src/coding/deliveryCheck.js.map +1 -0
  7. package/dist/src/coding/permissionGate.d.ts +6 -0
  8. package/dist/src/coding/permissionGate.js +8 -2
  9. package/dist/src/coding/permissionGate.js.map +1 -1
  10. package/dist/src/coding/projectContext.js +11 -1
  11. package/dist/src/coding/projectContext.js.map +1 -1
  12. package/dist/src/commands/configCommand.js +9 -3
  13. package/dist/src/commands/configCommand.js.map +1 -1
  14. package/dist/src/commands/execCommand.js +18 -1
  15. package/dist/src/commands/execCommand.js.map +1 -1
  16. package/dist/src/commands/mediaCommand.d.ts +1 -0
  17. package/dist/src/commands/mediaCommand.js +3 -0
  18. package/dist/src/commands/mediaCommand.js.map +1 -1
  19. package/dist/src/config/configFile.d.ts +4 -0
  20. package/dist/src/config/configFile.js +4 -0
  21. package/dist/src/config/configFile.js.map +1 -1
  22. package/dist/src/core/compaction.d.ts +6 -1
  23. package/dist/src/core/compaction.js +23 -5
  24. package/dist/src/core/compaction.js.map +1 -1
  25. package/dist/src/core/httpChatBackend.js +72 -20
  26. package/dist/src/core/httpChatBackend.js.map +1 -1
  27. package/dist/src/core/progressGuard.d.ts +16 -0
  28. package/dist/src/core/progressGuard.js +197 -0
  29. package/dist/src/core/progressGuard.js.map +1 -0
  30. package/dist/src/core/runChat.d.ts +8 -0
  31. package/dist/src/core/runChat.js +23 -5
  32. package/dist/src/core/runChat.js.map +1 -1
  33. package/dist/src/core/tokenBudget.d.ts +45 -0
  34. package/dist/src/core/tokenBudget.js +111 -0
  35. package/dist/src/core/tokenBudget.js.map +1 -0
  36. package/dist/src/core/turnLoop.d.ts +7 -1
  37. package/dist/src/core/turnLoop.js +108 -44
  38. package/dist/src/core/turnLoop.js.map +1 -1
  39. package/dist/src/core/types.d.ts +21 -0
  40. package/dist/src/exec/output.d.ts +6 -0
  41. package/dist/src/exec/output.js +26 -11
  42. package/dist/src/exec/output.js.map +1 -1
  43. package/dist/src/index.js +2 -0
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/mcp/mediaTools.js +14 -4
  46. package/dist/src/mcp/mediaTools.js.map +1 -1
  47. package/dist/src/media/aiApiClient.d.ts +7 -0
  48. package/dist/src/media/aiApiClient.js +21 -2
  49. package/dist/src/media/aiApiClient.js.map +1 -1
  50. package/dist/src/media/executor.d.ts +1 -1
  51. package/dist/src/media/executor.js +13 -7
  52. package/dist/src/media/executor.js.map +1 -1
  53. package/dist/src/media/extModels.d.ts +14 -0
  54. package/dist/src/media/extModels.js +204 -0
  55. package/dist/src/media/extModels.js.map +1 -0
  56. package/dist/src/media/generateImageTool.js +6 -0
  57. package/dist/src/media/generateImageTool.js.map +1 -1
  58. package/dist/src/media/manifest.js +34 -4
  59. package/dist/src/media/manifest.js.map +1 -1
  60. package/dist/src/media/taskManager.js +3 -1
  61. package/dist/src/media/taskManager.js.map +1 -1
  62. package/dist/src/media/types.d.ts +14 -0
  63. package/dist/src/media/types.js.map +1 -1
  64. package/dist/src/tui/App.js +9 -3
  65. package/dist/src/tui/App.js.map +1 -1
  66. package/dist/src/tui/chatController.d.ts +11 -1
  67. package/dist/src/tui/chatController.js +93 -15
  68. package/dist/src/tui/chatController.js.map +1 -1
  69. package/dist/src/tui/run.js +1 -0
  70. package/dist/src/tui/run.js.map +1 -1
  71. package/dist/src/tui/slashMedia.d.ts +2 -0
  72. package/dist/src/tui/slashMedia.js +4 -1
  73. package/dist/src/tui/slashMedia.js.map +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,197 @@
1
+ import { createHash } from 'node:crypto';
2
+ const WARN_AT = 3;
3
+ const PAUSE_AT = 5;
4
+ const MAX_TRACKED_OPERATIONS = 256;
5
+ const MUTATION_TOOLS = new Set(['write_file', 'edit_file', 'bash_kill']);
6
+ const NOTICE_FIELDS = new Set(['repeated_call_notice', 'progress_notice', 'context_pruning_notice']);
7
+ /**
8
+ * Per-turn evidence of repetition, not a judgment that the overall task is solved.
9
+ * Unrelated reads/planning do not erase a stalled operation. A changed result resets
10
+ * that operation; a successful new file mutation makes earlier observations stale.
11
+ * Store only bounded maps of hashes, never complete commands or tool output.
12
+ */
13
+ export class ProgressGuard {
14
+ observations = new Map();
15
+ mutations = new Map();
16
+ observe(name, rawArgs, result) {
17
+ const args = parseArguments(rawArgs);
18
+ if (name === 'update_plan' || isRunningPoll(name, args, result))
19
+ return;
20
+ const argumentHash = fingerprint(args);
21
+ const substantive = Object.fromEntries(Object.entries(result).filter(([key]) => !NOTICE_FIELDS.has(key)));
22
+ const resultHash = fingerprint(substantive);
23
+ if (!argumentHash || !resultHash)
24
+ return; // Unserializable/deep values cannot be compared safely.
25
+ const failed = typeof result.error === 'string' || result.cancelled === true;
26
+ if (!failed && MUTATION_TOOLS.has(name)) {
27
+ // A write following an edit to the same file is new progress even when it
28
+ // restores an earlier payload. Compare with the latest mutation of that
29
+ // target, rather than the last invocation of each mutation tool.
30
+ const target = fingerprint(name === 'bash_kill'
31
+ ? { kind: 'task', task_id: args.task_id }
32
+ : { kind: 'file', path: args.path });
33
+ const mutation = `${name}:${argumentHash}`;
34
+ if (target && this.mutations.get(target) !== mutation) {
35
+ this.observations.clear();
36
+ remember(this.mutations, target, mutation);
37
+ }
38
+ }
39
+ let notice = this.record(`call:${fingerprint(name)}:${argumentHash}`, resultHash, name, 'same arguments and result', evidence(result));
40
+ if (name === 'bash' && typeof args.command === 'string') {
41
+ const scope = bashScope(args.command);
42
+ if (scope) {
43
+ const key = `bash-error:${scope}`;
44
+ if (typeof result.error === 'string' && result.error.trim()) {
45
+ const diagnostic = bashDiagnostic(result.error);
46
+ const diagnosticHash = fingerprint({ diagnostic, exit_code: result.exit_code, signal: result.signal });
47
+ if (diagnosticHash) {
48
+ const failure = this.record(key, diagnosticHash, name, 'same failure for the same command target', diagnostic);
49
+ if (failure && (!notice || failure.kind === 'pause'))
50
+ notice = failure;
51
+ }
52
+ }
53
+ else if (!failed) {
54
+ this.observations.delete(key);
55
+ }
56
+ }
57
+ }
58
+ return notice;
59
+ }
60
+ record(key, result, name, detail, sample) {
61
+ const prior = this.observations.get(key);
62
+ const count = prior?.result === result ? prior.count + 1 : 1;
63
+ remember(this.observations, key, { result, count });
64
+ if (count !== WARN_AT && count !== PAUSE_AT)
65
+ return;
66
+ const kind = count === PAUSE_AT ? 'pause' : 'warning';
67
+ return {
68
+ kind,
69
+ message: `${shortText(name)} has returned the ${detail} ${count} times without observable progress` +
70
+ `${sample ? `: ${shortText(sample)}` : '.'} ` +
71
+ (kind === 'pause'
72
+ ? 'Further tool calls are paused so you can review the blocker and unfinished work.'
73
+ : 'Change the approach before trying again; two more unchanged results will pause this turn.'),
74
+ };
75
+ }
76
+ }
77
+ function remember(map, key, value) {
78
+ map.delete(key);
79
+ map.set(key, value);
80
+ if (map.size > MAX_TRACKED_OPERATIONS)
81
+ map.delete(map.keys().next().value);
82
+ }
83
+ function parseArguments(raw) {
84
+ try {
85
+ const parsed = JSON.parse(raw || '{}');
86
+ if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed))
87
+ return parsed;
88
+ }
89
+ catch { /* Invalid JSON can still repeat; preserve its identity below. */ }
90
+ return { unparsed_arguments: raw };
91
+ }
92
+ /** Hash all string bytes, including long output tails, without retaining/serializing a second copy. */
93
+ function fingerprint(value) {
94
+ const hash = createHash('sha256');
95
+ const seen = new WeakSet();
96
+ let nodes = 0;
97
+ const visit = (item, depth) => {
98
+ if (++nodes > 20_000 || depth > 64)
99
+ throw new Error('Fingerprint limit');
100
+ if (item === null) {
101
+ hash.update('null;');
102
+ return;
103
+ }
104
+ if (typeof item !== 'object') {
105
+ const text = String(item);
106
+ hash.update(`${typeof item}:${Buffer.byteLength(text)}:`).update(text).update(';');
107
+ return;
108
+ }
109
+ if (seen.has(item))
110
+ throw new Error('Cyclic result');
111
+ seen.add(item);
112
+ if (Array.isArray(item)) {
113
+ hash.update('array[');
114
+ for (const child of item)
115
+ visit(child, depth + 1);
116
+ }
117
+ else {
118
+ const prototype = Object.getPrototypeOf(item);
119
+ if (prototype !== Object.prototype && prototype !== null)
120
+ throw new Error('Non-JSON result');
121
+ hash.update('object{');
122
+ for (const key of Object.keys(item).sort()) {
123
+ visit(key, depth + 1);
124
+ visit(item[key], depth + 1);
125
+ }
126
+ }
127
+ hash.update('};');
128
+ seen.delete(item);
129
+ };
130
+ try {
131
+ visit(value, 0);
132
+ return hash.digest('hex');
133
+ }
134
+ catch {
135
+ return;
136
+ }
137
+ }
138
+ function isRunningPoll(name, args, result) {
139
+ if (typeof result.error === 'string' || result.cancelled === true || typeof args.task_id !== 'string')
140
+ return false;
141
+ if (name === 'bash_output') {
142
+ // The current bash_output contract appends this exact status line to its output.
143
+ return result.status === 'running' ||
144
+ (typeof result.output === 'string' && /(?:^|\n)Status: running\s*$/.test(result.output));
145
+ }
146
+ // Only the actual media polling tool is exempt, never generate_* submissions or
147
+ // arbitrary tools that happen to include a status/task_id field.
148
+ return name === 'glbgpt_check_task' && result.status === 'generating';
149
+ }
150
+ /** A stable execution target for Python retries; unrelated scripts retain separate histories. */
151
+ function bashScope(command) {
152
+ const firstLine = command.trim().split(/\r?\n/, 1)[0];
153
+ const tokens = firstLine.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
154
+ const firstToken = tokens[0];
155
+ if (!firstToken)
156
+ return;
157
+ const executable = firstToken.split('/').at(-1) ?? '';
158
+ if (/^python(?:\d+(?:\.\d+)*)?$/.test(executable)) {
159
+ // Keep interpreter flags/module/script selection, but not inline source or heredoc body.
160
+ const target = [firstToken];
161
+ for (const token of tokens.slice(1)) {
162
+ if (token === '-c' || token === '-' || token.startsWith('<<')) {
163
+ target.push(token === '-c' ? '-c' : 'stdin');
164
+ return fingerprint(target);
165
+ }
166
+ target.push(token);
167
+ if (!token.startsWith('-'))
168
+ return fingerprint(target);
169
+ }
170
+ }
171
+ // For other commands, only identical first command lines share an error history.
172
+ // Exact invocation/result tracking above still handles their ordinary repetitions.
173
+ return fingerprint(firstLine);
174
+ }
175
+ function bashDiagnostic(error) {
176
+ const tail = error.slice(-16_384).replace(/\u001b\[[0-9;]*m/g, '');
177
+ const lines = tail.split(/\r?\n/).map(line => line.trim()).filter(Boolean);
178
+ let diagnostic;
179
+ for (let i = lines.length - 1; i >= 0; i--) {
180
+ if (/^[\w.]+(?:Error|Exception):\s*\S/.test(lines[i])) {
181
+ diagnostic = lines[i];
182
+ break;
183
+ }
184
+ }
185
+ return diagnostic ?? error.trim();
186
+ }
187
+ function evidence(result) {
188
+ for (const key of ['error', 'output', 'summary', 'content']) {
189
+ if (typeof result[key] === 'string')
190
+ return result[key];
191
+ }
192
+ return '';
193
+ }
194
+ function shortText(text) {
195
+ return text.slice(0, 300).replace(/\u001b\[[0-9;]*m/g, '').replace(/[\x00-\x1f\x7f]+/g, ' ').trim().slice(0, 180);
196
+ }
197
+ //# sourceMappingURL=progressGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progressGuard.js","sourceRoot":"","sources":["../../../src/core/progressGuard.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { ToolRegistry } from './toolRegistry.js';
2
+ import type { TaskAssessment, PauseReason } from './types.js';
2
3
  import { type OutputSchemaSpec } from './structuredOutput.js';
3
4
  import type { ChatBackend, ContentPart, EngineEvent } from './types.js';
4
5
  /** One chat-only turn's result. `structured` is set when a response schema was requested. */
@@ -9,6 +10,7 @@ export interface ChatTurnResult {
9
10
  prompt_tokens: number;
10
11
  completion_tokens: number;
11
12
  };
13
+ task?: TaskAssessment;
12
14
  }
13
15
  export interface ChatTurnParams {
14
16
  backend: ChatBackend;
@@ -29,6 +31,8 @@ export interface ChatTurnParams {
29
31
  responseFormat?: unknown;
30
32
  /** Client-side tools available to this turn. Defaults to an empty registry (no tools). */
31
33
  registry?: ToolRegistry;
34
+ maxTurnTokens?: number;
35
+ assessTask?: () => Promise<TaskAssessment | undefined>;
32
36
  }
33
37
  /**
34
38
  * Run a single chat turn (client tools optional via `params.registry`; defaults to none)
@@ -40,3 +44,7 @@ export interface ChatTurnParams {
40
44
  * Throws on a backend/turn error or when structured output does not parse.
41
45
  */
42
46
  export declare function runChatTurn(params: ChatTurnParams, onEvent?: (event: EngineEvent) => void | Promise<void>): Promise<ChatTurnResult>;
47
+ export declare class TurnPausedError extends Error {
48
+ readonly reason: PauseReason;
49
+ constructor(reason: PauseReason, message: string);
50
+ }
@@ -1,6 +1,7 @@
1
1
  import { Session } from './session.js';
2
2
  import { TurnLoop } from './turnLoop.js';
3
3
  import { ToolRegistry } from './toolRegistry.js';
4
+ import { TurnTokenBudget } from './tokenBudget.js';
4
5
  import { newId } from './id.js';
5
6
  import { planStructuredOutput, planForMode, parseStructuredJson, stripSyntheticNulls, isResponseFormatRejection, } from './structuredOutput.js';
6
7
  /**
@@ -14,7 +15,8 @@ import { planStructuredOutput, planForMode, parseStructuredJson, stripSyntheticN
14
15
  */
15
16
  export async function runChatTurn(params, onEvent) {
16
17
  const plan = params.outputSchema ? planStructuredOutput(params.model, params.outputSchema) : undefined;
17
- const first = await runOnce(params, plan, onEvent);
18
+ const tokenBudget = new TurnTokenBudget(params.maxTurnTokens);
19
+ const first = await runOnce(params, plan, tokenBudget, onEvent);
18
20
  const failure = first.thrown instanceof Error ? first.thrown.message : first.engineError;
19
21
  if (failure &&
20
22
  plan?.mode === 'json_schema' &&
@@ -25,7 +27,7 @@ export async function runChatTurn(params, onEvent) {
25
27
  // regardless of the flag, others lack json_schema entirely). Nothing streamed, the failed
26
28
  // call is refunded server-side → one retry in json_object mode with the schema in the prompt.
27
29
  const fallback = planForMode('json_object', params.outputSchema);
28
- const second = await runOnce(params, fallback, onEvent);
30
+ const second = await runOnce(params, fallback, tokenBudget, onEvent);
29
31
  // Keep any usage the rejected request reported, matching the streamed usage events.
30
32
  // These are token totals, not a claim about whether the backend charged/refunded it.
31
33
  second.usage.prompt_tokens += first.usage.prompt_tokens;
@@ -34,7 +36,7 @@ export async function runChatTurn(params, onEvent) {
34
36
  }
35
37
  return finish(first, plan, params);
36
38
  }
37
- async function runOnce(params, plan, onEvent) {
39
+ async function runOnce(params, plan, tokenBudget, onEvent) {
38
40
  const session = new Session({ id: newId(), model: params.model });
39
41
  const responseFormat = plan ? plan.responseFormat : params.responseFormat;
40
42
  const system = [params.system?.trim(), plan?.systemAddendum].filter((s) => !!s).join('\n\n');
@@ -63,6 +65,12 @@ async function runOnce(params, plan, onEvent) {
63
65
  result.usage.prompt_tokens += event.promptTokens;
64
66
  result.usage.completion_tokens += event.completionTokens;
65
67
  break;
68
+ case 'turn_paused':
69
+ result.paused = event;
70
+ break;
71
+ case 'task_status':
72
+ result.task = event.assessment;
73
+ break;
66
74
  case 'error':
67
75
  result.engineError = event.message;
68
76
  break;
@@ -71,7 +79,7 @@ async function runOnce(params, plan, onEvent) {
71
79
  // turn_complete is implicit in runTurn resolving.
72
80
  }
73
81
  return onEvent?.(event);
74
- }, { responseFormat });
82
+ }, { responseFormat, tokenBudget, assessTask: params.assessTask });
75
83
  }
76
84
  catch (err) {
77
85
  result.thrown = err;
@@ -79,6 +87,8 @@ async function runOnce(params, plan, onEvent) {
79
87
  return result;
80
88
  }
81
89
  function finish(r, plan, params) {
90
+ if (r.paused)
91
+ throw new TurnPausedError(r.paused.reason, r.paused.message);
82
92
  if (r.thrown !== undefined)
83
93
  throw r.thrown;
84
94
  if (r.engineError)
@@ -90,6 +100,14 @@ function finish(r, plan, params) {
90
100
  if (params.outputSchema)
91
101
  stripSyntheticNulls(structured, params.outputSchema.schema);
92
102
  }
93
- return { content: r.content, structured, usage: r.usage };
103
+ return { content: r.content, structured, usage: r.usage, ...(r.task ? { task: r.task } : {}) };
104
+ }
105
+ export class TurnPausedError extends Error {
106
+ reason;
107
+ constructor(reason, message) {
108
+ super(message);
109
+ this.reason = reason;
110
+ this.name = 'TurnPausedError';
111
+ }
94
112
  }
95
113
  //# sourceMappingURL=runChat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runChat.js","sourceRoot":"","sources":["../../../src/core/runChat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runChat.js","sourceRoot":"","sources":["../../../src/core/runChat.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { ChatChunk, ChatMessage, ChatRequest } from './types.js';
2
+ export declare const DEFAULT_MAX_TURN_TOKENS = 200000;
3
+ export declare const MIN_MAX_TURN_TOKENS = 1000;
4
+ export declare const MAX_MAX_TURN_TOKENS = 10000000;
5
+ export declare function parseMaxTurnTokens(value: string | number, source?: string): number;
6
+ /** Explicit turn option > environment > saved configuration > the bounded default. */
7
+ export declare function resolveMaxTurnTokens(override?: number): number;
8
+ /**
9
+ * Approximate input size: text uses the same bytes/4 heuristic as context compaction;
10
+ * image parts use a bounded vision allowance instead of tokenizing their URL/base64.
11
+ * Include message framing, tool definitions and output schemas because they are resent
12
+ * on each request. Provider tokenization, image tokens and server prompts can differ.
13
+ */
14
+ export declare function estimateRequestTokens(req: ChatRequest): number;
15
+ export interface TokenBudgetSnapshot {
16
+ usedTokens: number;
17
+ limitTokens: number;
18
+ remainingTokens: number;
19
+ /** True when any missing provider usage was replaced by a local estimate. */
20
+ estimated: boolean;
21
+ }
22
+ export interface TokenBudgetPreflight extends TokenBudgetSnapshot {
23
+ allowed: boolean;
24
+ reason?: 'exhausted' | 'request_too_large';
25
+ estimatedPromptTokens: number;
26
+ }
27
+ /**
28
+ * Cumulative request-token protection for one user turn, including tool rounds and retries.
29
+ * Share this instance across retries; record each request once using its final usage snapshot.
30
+ * This pauses further requests, not an in-flight response, and is not a hard billing cap.
31
+ */
32
+ export declare class TurnTokenBudget {
33
+ readonly limitTokens: number;
34
+ private usedTokens;
35
+ private estimated;
36
+ private warned;
37
+ constructor(limit?: number);
38
+ snapshot(): TokenBudgetSnapshot;
39
+ /** Check the exact request projection about to be sent, after pruning and any added prompts. */
40
+ preflight(req: ChatRequest): TokenBudgetPreflight;
41
+ /** Call once per model request, also on interruption/failure when some usage may be known. */
42
+ recordCall(req: ChatRequest, output: Pick<ChatMessage, 'content' | 'tool_calls'>, usage?: ChatChunk['usage']): TokenBudgetSnapshot;
43
+ /** Emit at most one warning per turn when cumulative usage reaches 80% of the limit. */
44
+ takeWarning(): TokenBudgetSnapshot | null;
45
+ }
@@ -0,0 +1,111 @@
1
+ import { getConfigValueSync } from '../config/configFile.js';
2
+ import { estimateMessageTokens } from './compaction.js';
3
+ export const DEFAULT_MAX_TURN_TOKENS = 200_000;
4
+ export const MIN_MAX_TURN_TOKENS = 1_000;
5
+ export const MAX_MAX_TURN_TOKENS = 10_000_000;
6
+ // Cross-model headroom for one vision input, not a provider token formula or upper bound.
7
+ // Actual image usage depends on the model, dimensions, detail and preprocessing, not the
8
+ // compressed file size or base64 length. Keep each image's estimate bounded; count every
9
+ // image separately and replace estimates with provider usage whenever it is available.
10
+ const IMAGE_TOKEN_ESTIMATE = 16_384;
11
+ const LOW_DETAIL_IMAGE_TOKEN_ESTIMATE = 4_096;
12
+ export function parseMaxTurnTokens(value, source = 'max-turn-tokens') {
13
+ const raw = typeof value === 'string' ? value.trim() : value;
14
+ const parsed = typeof raw === 'string' && /^\d+$/.test(raw) ? Number(raw) : raw;
15
+ if (typeof parsed !== 'number' ||
16
+ !Number.isSafeInteger(parsed) ||
17
+ parsed < MIN_MAX_TURN_TOKENS ||
18
+ parsed > MAX_MAX_TURN_TOKENS) {
19
+ throw new Error(`${source} must be an integer between ${MIN_MAX_TURN_TOKENS} and ${MAX_MAX_TURN_TOKENS}.`);
20
+ }
21
+ return parsed;
22
+ }
23
+ /** Explicit turn option > environment > saved configuration > the bounded default. */
24
+ export function resolveMaxTurnTokens(override) {
25
+ if (override !== undefined)
26
+ return parseMaxTurnTokens(override, 'maxTurnTokens');
27
+ const env = process.env.GLBGPT_MAX_TURN_TOKENS;
28
+ if (env !== undefined)
29
+ return parseMaxTurnTokens(env, 'GLBGPT_MAX_TURN_TOKENS');
30
+ const stored = getConfigValueSync('max-turn-tokens');
31
+ return stored === undefined ? DEFAULT_MAX_TURN_TOKENS : parseMaxTurnTokens(stored);
32
+ }
33
+ /**
34
+ * Approximate input size: text uses the same bytes/4 heuristic as context compaction;
35
+ * image parts use a bounded vision allowance instead of tokenizing their URL/base64.
36
+ * Include message framing, tool definitions and output schemas because they are resent
37
+ * on each request. Provider tokenization, image tokens and server prompts can differ.
38
+ */
39
+ export function estimateRequestTokens(req) {
40
+ const messages = req.messages.reduce((sum, message) => sum + estimateBudgetMessageTokens(message) + 4, 3);
41
+ return messages + estimateJsonTokens(req.tools) + estimateJsonTokens(req.response_format);
42
+ }
43
+ /** Never serialize image content: a supported 8 MiB image is not millions of text tokens. */
44
+ function estimateBudgetMessageTokens(message) {
45
+ if (!Array.isArray(message.content))
46
+ return estimateMessageTokens(message);
47
+ const toolTokens = estimateMessageTokens({ ...message, content: null });
48
+ return message.content.reduce((sum, part) => {
49
+ if (part.type === 'text')
50
+ return sum + estimateMessageTokens({ role: message.role, content: part.text });
51
+ // `detail` is optional on OpenAI-compatible image inputs; older CLI types omit it.
52
+ const lowDetail = 'detail' in part.image_url && part.image_url.detail === 'low';
53
+ return sum + (lowDetail ? LOW_DETAIL_IMAGE_TOKEN_ESTIMATE : IMAGE_TOKEN_ESTIMATE);
54
+ }, toolTokens);
55
+ }
56
+ function estimateJsonTokens(value) {
57
+ if (value === undefined || value === null)
58
+ return 0;
59
+ return Math.ceil(Buffer.byteLength(JSON.stringify(value), 'utf8') / 4);
60
+ }
61
+ /**
62
+ * Cumulative request-token protection for one user turn, including tool rounds and retries.
63
+ * Share this instance across retries; record each request once using its final usage snapshot.
64
+ * This pauses further requests, not an in-flight response, and is not a hard billing cap.
65
+ */
66
+ export class TurnTokenBudget {
67
+ limitTokens;
68
+ usedTokens = 0;
69
+ estimated = false;
70
+ warned = false;
71
+ constructor(limit = resolveMaxTurnTokens()) {
72
+ this.limitTokens = parseMaxTurnTokens(limit, 'maxTurnTokens');
73
+ }
74
+ snapshot() {
75
+ return {
76
+ usedTokens: this.usedTokens,
77
+ limitTokens: this.limitTokens,
78
+ remainingTokens: Math.max(0, this.limitTokens - this.usedTokens),
79
+ estimated: this.estimated,
80
+ };
81
+ }
82
+ /** Check the exact request projection about to be sent, after pruning and any added prompts. */
83
+ preflight(req) {
84
+ const snapshot = this.snapshot();
85
+ const estimatedPromptTokens = estimateRequestTokens(req);
86
+ const reason = snapshot.remainingTokens === 0
87
+ ? 'exhausted'
88
+ : estimatedPromptTokens > snapshot.remainingTokens ? 'request_too_large' : undefined;
89
+ return { ...snapshot, estimatedPromptTokens, allowed: reason === undefined, ...(reason ? { reason } : {}) };
90
+ }
91
+ /** Call once per model request, also on interruption/failure when some usage may be known. */
92
+ recordCall(req, output, usage) {
93
+ const prompt = knownTokens(usage?.prompt_tokens);
94
+ const completion = knownTokens(usage?.completion_tokens);
95
+ this.estimated ||= prompt === undefined || completion === undefined;
96
+ this.usedTokens += (prompt ?? estimateRequestTokens(req)) +
97
+ (completion ?? estimateBudgetMessageTokens({ role: 'assistant', ...output }));
98
+ return this.snapshot();
99
+ }
100
+ /** Emit at most one warning per turn when cumulative usage reaches 80% of the limit. */
101
+ takeWarning() {
102
+ if (this.warned || this.usedTokens < this.limitTokens * 0.8)
103
+ return null;
104
+ this.warned = true;
105
+ return this.snapshot();
106
+ }
107
+ }
108
+ function knownTokens(value) {
109
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
110
+ }
111
+ //# sourceMappingURL=tokenBudget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenBudget.js","sourceRoot":"","sources":["../../../src/core/tokenBudget.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
- import type { ChatBackend, EngineEvent } from './types.js';
1
+ import type { ChatBackend, EngineEvent, TaskAssessment } from './types.js';
2
2
  import type { Session } from './session.js';
3
3
  import type { ToolRegistry } from './toolRegistry.js';
4
+ import { TurnTokenBudget } from './tokenBudget.js';
4
5
  /**
5
6
  * Event sink. A returned promise applies BACKPRESSURE on the streaming pump (pi A3):
6
7
  * consumeStream awaits it before pulling the next chunk, so a slow consumer (exec piping
@@ -16,6 +17,11 @@ export interface TurnOptions {
16
17
  includeGatedTools?: boolean;
17
18
  /** Hard cap on tool calls dispatched in this turn. Defaults to the configured max-tool-calls budget. */
18
19
  maxToolCalls?: number;
20
+ /** Shared across safe retries of a single user turn. */
21
+ tokenBudget?: TurnTokenBudget;
22
+ maxTurnTokens?: number;
23
+ /** Local, read-only delivery check. Never sends another model request. */
24
+ assessTask?: () => Promise<TaskAssessment | undefined>;
19
25
  /**
20
26
  * Steering (pi B2): called at each round boundary AFTER tool results are appended and
21
27
  * BEFORE the next model call. Returned items are appended as user messages, so text the