praxis-agent 0.62.0 → 0.62.2
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 +17 -3
- package/dist/cli/interactive.d.ts +1 -0
- package/dist/cli/interactive.js +6 -4
- package/dist/cli/tui/claude-style.js +10 -1
- package/dist/cli/tui/conversation-export.js +3 -1
- package/dist/cli/tui/transcript-presentation.d.ts +1 -1
- package/dist/cli/tui/transcript-viewport.js +23 -0
- package/dist/cli/tui/tui-row-ir.js +3 -1
- package/dist/cli-runtime.d.ts +1 -0
- package/dist/cli-runtime.js +36 -6
- package/dist/core/context-budget.js +8 -4
- package/dist/providers/anthropic-compatible.d.ts +7 -0
- package/dist/providers/anthropic-compatible.js +40 -22
- package/dist/providers/anthropic-model-spec.d.ts +10 -0
- package/dist/providers/anthropic-model-spec.js +16 -0
- package/dist/providers/provider-registry.d.ts +6 -1
- package/dist/providers/provider-registry.js +10 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -98,6 +98,16 @@ cd /path/to/project
|
|
|
98
98
|
praxis
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
Anthropic models use a 200,000-token context window by default, including
|
|
102
|
+
unknown model IDs. Add the exact terminal `[1m]` suffix (for example,
|
|
103
|
+
`claude-sonnet-4-20250514[1m]`) to request a 1,000,000-token context window;
|
|
104
|
+
Praxis keeps that selected model public, removes the suffix on the wire, and
|
|
105
|
+
adds the `context-1m-2025-08-07` Anthropic beta once. An explicit
|
|
106
|
+
`PRAXIS_CONTEXT_WINDOW_TOKENS` value overrides either inferred window.
|
|
107
|
+
Exact `claude-sonnet-4-6` and `claude-opus-4-6` selections also support
|
|
108
|
+
`--thinking adaptive`; use `--thinking enabled --max-thinking-tokens <n>`
|
|
109
|
+
when a fixed thinking-token budget is required.
|
|
110
|
+
|
|
101
111
|
Common non-interactive operations:
|
|
102
112
|
|
|
103
113
|
```sh
|
|
@@ -159,7 +169,9 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
159
169
|
resize-safe lifecycle, Ctrl-C restoration, fullscreen `Ctrl+L` redraw, and
|
|
160
170
|
mouse-wheel/drag selection with edge autoscroll and OSC 52 copy,
|
|
161
171
|
interactive `/doctor` diagnostics, per-session model/effort/permission controls,
|
|
162
|
-
context/status/skill/task dashboards
|
|
172
|
+
context/status/skill/task dashboards whose context view previews the selected
|
|
173
|
+
provider/model capacity before the first turn and reports unavailable capacity
|
|
174
|
+
without fabricated percentages, prompt stash and continuation shortcuts,
|
|
163
175
|
filterable `@` file and agent references, composer undo, `Ctrl+G` external
|
|
164
176
|
editing, shared `/keybindings` creation/editing and supported-action remapping,
|
|
165
177
|
shared built-in and custom `/theme` profiles with immediate
|
|
@@ -177,7 +189,7 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
177
189
|
patches and readable binary/conflict/transient-path notes, semantic plan/question decision panels with complete
|
|
178
190
|
screen-reader actions, semantic screen projection across selectable surfaces,
|
|
179
191
|
deterministic resize-aware URL/form elicitation rendering, and measured
|
|
180
|
-
context budgets; print mode,
|
|
192
|
+
context budgets with base64-payload-independent image estimates; print mode,
|
|
181
193
|
structured JSON/JSONL, context compaction, tool loops, and bounded execution.
|
|
182
194
|
- **Built-in tools** — read, write, edit, `ApplyPatch` for bounded ordered exact
|
|
183
195
|
multi-file replacements, configured plugin LSP navigation with fresh bounded
|
|
@@ -294,7 +306,9 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
294
306
|
route, whether primary or fallback, stays sticky only through that logical
|
|
295
307
|
Turn's tool continuations; incompatible routes fail closed, and the next
|
|
296
308
|
independent Turn starts from primary. Recovery may persist only an optional
|
|
297
|
-
selected model, never provider route or wire state.
|
|
309
|
+
selected model, never provider route or wire state. Anthropic uses a
|
|
310
|
+
200,000-token default or exact terminal `[1m]` model syntax for 1,000,000
|
|
311
|
+
tokens; `PRAXIS_CONTEXT_WINDOW_TOKENS` overrides the advertised window.
|
|
298
312
|
- **Transactional self-update** — `praxis update` verifies the package before
|
|
299
313
|
installing it, rejects concurrent updates, and can roll back after an
|
|
300
314
|
interruption or crash.
|
|
@@ -85,6 +85,7 @@ interface InteractiveSessionCommands {
|
|
|
85
85
|
}
|
|
86
86
|
export interface InteractiveServiceFactory {
|
|
87
87
|
scheduledPrompts?: boolean;
|
|
88
|
+
contextWindowTokens?(modelId?: string): number | undefined;
|
|
88
89
|
createService(options: {
|
|
89
90
|
eventSink: RuntimeEventSink;
|
|
90
91
|
requireProvider: boolean;
|
package/dist/cli/interactive.js
CHANGED
|
@@ -471,7 +471,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
471
471
|
const [, setTurnDuration] = useState();
|
|
472
472
|
const [usage, setUsage] = useState();
|
|
473
473
|
const [costUsd, setCostUsd] = useState();
|
|
474
|
-
const [contextWindowTokens, setContextWindowTokens] = useState(display.contextWindowTokens);
|
|
474
|
+
const [contextWindowTokens, setContextWindowTokens] = useState(display.contextWindowTokens ?? factory.contextWindowTokens?.());
|
|
475
475
|
const [historyState, setHistoryState] = useState(() => {
|
|
476
476
|
const items = [...initialHistory];
|
|
477
477
|
return {
|
|
@@ -2237,8 +2237,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
2237
2237
|
setAvailableSlashCommands(created.slashCommands?.() ?? slashCommands);
|
|
2238
2238
|
setAvailableAgents(created.agentDefinitions?.() ?? agents);
|
|
2239
2239
|
const runtimeInfo = created.runtimeInfo?.();
|
|
2240
|
-
|
|
2241
|
-
setContextWindowTokens(runtimeInfo.contextWindowTokens);
|
|
2240
|
+
setContextWindowTokens(runtimeInfo?.contextWindowTokens);
|
|
2242
2241
|
return created;
|
|
2243
2242
|
}
|
|
2244
2243
|
finally {
|
|
@@ -2314,6 +2313,7 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
2314
2313
|
void loading.finally(() => onTurnChange?.(null));
|
|
2315
2314
|
};
|
|
2316
2315
|
const changeModel = (model) => {
|
|
2316
|
+
setContextWindowTokens(factory.contextWindowTokens?.(model));
|
|
2317
2317
|
updateRuntimePreferences((current) => {
|
|
2318
2318
|
if (model !== undefined)
|
|
2319
2319
|
return { ...current, model };
|
|
@@ -6726,7 +6726,9 @@ export function InteractiveApp({ dataPlane = resolveDataPlane(), configRoot: sup
|
|
|
6726
6726
|
const contextEntry = {
|
|
6727
6727
|
kind: 'context',
|
|
6728
6728
|
usedTokens: Math.max(measuredTokens, skillTokens),
|
|
6729
|
-
|
|
6729
|
+
...(runtimeDisplay.contextWindowTokens === undefined
|
|
6730
|
+
? {}
|
|
6731
|
+
: { contextWindowTokens: runtimeDisplay.contextWindowTokens }),
|
|
6730
6732
|
model: runtimeDisplay.model ?? 'provider default',
|
|
6731
6733
|
skills,
|
|
6732
6734
|
memoryFiles: [],
|
|
@@ -390,6 +390,15 @@ function percent(tokens, total) {
|
|
|
390
390
|
}
|
|
391
391
|
function ContextUsageBlock({ usedTokens, contextWindowTokens, model, skills, memoryFiles, screenReader, }) {
|
|
392
392
|
const theme = useTuiTheme();
|
|
393
|
+
const resourceTree = (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { ...theme.text.heading, bold: true, children: "Memory files \u00B7 /memory" }), memoryFiles.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u2514 No memory files" })) : (memoryFiles.map((file, index) => (_jsxs(Text, { dimColor: true, children: [index === memoryFiles.length - 1 ? '└' : '├', " ", file.path, ":", ' ', file.tokens, " tokens"] }, file.path)))), _jsx(Text, { children: " " }), _jsx(Text, { ...theme.text.heading, bold: true, children: "Skills \u00B7 /skills" }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Loaded" }), skills.length === 0 ? (_jsx(Text, { dimColor: true, children: "\u2514 No skills loaded" })) : (skills.map((skill, index) => (_jsxs(Text, { dimColor: true, children: [index === skills.length - 1 ? '└' : '├', " ", skill.name, ": ~", skill.tokens, " tokens"] }, skill.name))))] }));
|
|
394
|
+
if (screenReader && contextWindowTokens === undefined) {
|
|
395
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { ...theme.text.heading, children: "Context Usage" }), _jsxs(Text, { children: [model ?? 'provider default', " \u00B7 ", usedTokens.toLocaleString(), " tokens \u00B7 Context capacity unavailable"] }), _jsxs(Text, { children: ["Memory files:", ' ', memoryFiles
|
|
396
|
+
.map((file) => `${file.path} (${file.tokens} tokens)`)
|
|
397
|
+
.join(', ') || 'none'] }), _jsxs(Text, { children: ["Skills: ", skills.map(({ name }) => name).join(', ') || 'none'] })] }));
|
|
398
|
+
}
|
|
399
|
+
if (contextWindowTokens === undefined) {
|
|
400
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [_jsx(Text, { ...theme.text.heading, bold: true, children: "Context Usage" }), _jsxs(Text, { dimColor: true, children: [model ?? 'provider default', " \u00B7 ", usedTokens.toLocaleString(), " tokens \u00B7 Context capacity unavailable"] }), resourceTree] }));
|
|
401
|
+
}
|
|
393
402
|
const totalTokens = Math.max(1, contextWindowTokens);
|
|
394
403
|
const compactBuffer = Math.round(totalTokens * 0.165);
|
|
395
404
|
const usable = Math.max(1, totalTokens - compactBuffer);
|
|
@@ -399,7 +408,7 @@ function ContextUsageBlock({ usedTokens, contextWindowTokens, model, skills, mem
|
|
|
399
408
|
const usedCells = Math.min(25, Math.round((usedTokens / totalTokens) * 25));
|
|
400
409
|
const bufferCells = Math.min(25 - usedCells, Math.round((compactBuffer / totalTokens) * 25));
|
|
401
410
|
const cells = Array.from({ length: 25 }, (_, index) => index < usedCells ? '⛁' : index >= 25 - bufferCells ? '⛝' : '⛶');
|
|
402
|
-
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [_jsx(Text, { ...theme.text.heading, bold: true, children: "Context Usage" }), _jsxs(Box, { children: [_jsx(Box, { flexDirection: "column", marginRight: 2, children: Array.from({ length: 5 }, (_, row) => (_jsx(Text, { children: cells.slice(row * 5, row * 5 + 5).join(' ') }, row))) }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [model ?? 'provider default', " \u00B7 ", compactTokens(usedTokens), "/", compactTokens(totalTokens), " tokens (", percent(usedTokens, totalTokens), ")"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, italic: true, children: "Estimated usage by category" }), _jsxs(Text, { children: ["\u26C1 Messages and other context: ", compactTokens(usedTokens), " tokens (", percent(usedTokens, totalTokens), ")"] }), _jsxs(Text, { children: ["\u26F6 Free space: ", compactTokens(Math.max(0, usable - usedTokens)), " (", percent(Math.max(0, usable - usedTokens), totalTokens), ")"] }), _jsxs(Text, { children: ["\u26DD Autocompact buffer: ", compactTokens(compactBuffer), " tokens (", percent(compactBuffer, totalTokens), ")"] })] })] }),
|
|
411
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [_jsx(Text, { ...theme.text.heading, bold: true, children: "Context Usage" }), _jsxs(Box, { children: [_jsx(Box, { flexDirection: "column", marginRight: 2, children: Array.from({ length: 5 }, (_, row) => (_jsx(Text, { children: cells.slice(row * 5, row * 5 + 5).join(' ') }, row))) }), _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [model ?? 'provider default', " \u00B7 ", compactTokens(usedTokens), "/", compactTokens(totalTokens), " tokens (", percent(usedTokens, totalTokens), ")"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, italic: true, children: "Estimated usage by category" }), _jsxs(Text, { children: ["\u26C1 Messages and other context: ", compactTokens(usedTokens), " tokens (", percent(usedTokens, totalTokens), ")"] }), _jsxs(Text, { children: ["\u26F6 Free space: ", compactTokens(Math.max(0, usable - usedTokens)), " (", percent(Math.max(0, usable - usedTokens), totalTokens), ")"] }), _jsxs(Text, { children: ["\u26DD Autocompact buffer: ", compactTokens(compactBuffer), " tokens (", percent(compactBuffer, totalTokens), ")"] })] })] }), resourceTree] }));
|
|
403
412
|
}
|
|
404
413
|
const MARKDOWN_TEXT_CACHE_MAX = 2048;
|
|
405
414
|
const markdownTextCache = new Map();
|
|
@@ -32,7 +32,9 @@ export function conversationExportText(_display, items) {
|
|
|
32
32
|
output.push(...lines(item.text, ' ⎿ '));
|
|
33
33
|
}
|
|
34
34
|
else if (item.kind === 'context') {
|
|
35
|
-
output.push('',
|
|
35
|
+
output.push('', item.contextWindowTokens === undefined
|
|
36
|
+
? `Context Usage: ${item.usedTokens} tokens · Context capacity unavailable`
|
|
37
|
+
: `Context Usage: ${item.usedTokens}/${item.contextWindowTokens} tokens`);
|
|
36
38
|
}
|
|
37
39
|
else {
|
|
38
40
|
output.push(...lines(item.text, item.kind === 'warning' ? '⚠ ' : '· '));
|
|
@@ -262,6 +262,21 @@ function percent(tokens, total) {
|
|
|
262
262
|
return `${Math.round((tokens / Math.max(1, total)) * 100 * 10) / 10}%`;
|
|
263
263
|
}
|
|
264
264
|
function contextUsageLineCount(item, width) {
|
|
265
|
+
if (item.contextWindowTokens === undefined) {
|
|
266
|
+
const resourceWidth = Math.max(1, width - 2);
|
|
267
|
+
const memoryRows = item.memoryFiles.length
|
|
268
|
+
? item.memoryFiles.reduce((rows, file, index) => rows +
|
|
269
|
+
wrappedLineCount(`${index === item.memoryFiles.length - 1 ? '└' : '├'} ${file.path}: ${file.tokens} tokens`, resourceWidth), 0)
|
|
270
|
+
: wrappedLineCount('└ No memory files', resourceWidth);
|
|
271
|
+
const skillRows = item.skills.length
|
|
272
|
+
? item.skills.reduce((rows, skill, index) => rows +
|
|
273
|
+
wrappedLineCount(`${index === item.skills.length - 1 ? '└' : '├'} ${skill.name}: ~${skill.tokens} tokens`, resourceWidth), 0)
|
|
274
|
+
: wrappedLineCount('└ No skills loaded', resourceWidth);
|
|
275
|
+
return (8 +
|
|
276
|
+
wrappedLineCount(`${item.model ?? 'provider default'} · ${item.usedTokens.toLocaleString()} tokens · Context capacity unavailable`, resourceWidth) +
|
|
277
|
+
memoryRows +
|
|
278
|
+
skillRows);
|
|
279
|
+
}
|
|
265
280
|
const totalTokens = Math.max(1, item.contextWindowTokens);
|
|
266
281
|
const compactBuffer = Math.round(totalTokens * 0.165);
|
|
267
282
|
const usable = Math.max(1, totalTokens - compactBuffer);
|
|
@@ -367,6 +382,14 @@ function visibleOutputLineCount(text, width, mode) {
|
|
|
367
382
|
: 0));
|
|
368
383
|
}
|
|
369
384
|
function screenReaderContextLineCount(item, width) {
|
|
385
|
+
if (item.contextWindowTokens === undefined) {
|
|
386
|
+
return [
|
|
387
|
+
'Context Usage',
|
|
388
|
+
`${item.model ?? 'provider default'} · ${item.usedTokens.toLocaleString()} tokens · Context capacity unavailable`,
|
|
389
|
+
`Memory files: ${item.memoryFiles.map((file) => `${file.path} (${file.tokens} tokens)`).join(', ') || 'none'}`,
|
|
390
|
+
`Skills: ${item.skills.map(({ name }) => name).join(', ') || 'none'}`,
|
|
391
|
+
].reduce((rows, line) => rows + wrappedLineCount(line, width), 0);
|
|
392
|
+
}
|
|
370
393
|
const totalTokens = Math.max(1, item.contextWindowTokens);
|
|
371
394
|
const compactBuffer = Math.round(totalTokens * 0.165);
|
|
372
395
|
return [
|
|
@@ -38,7 +38,9 @@ function entryText(entry) {
|
|
|
38
38
|
};
|
|
39
39
|
if (item.kind === 'context')
|
|
40
40
|
return {
|
|
41
|
-
text:
|
|
41
|
+
text: item.contextWindowTokens === undefined
|
|
42
|
+
? `Context Usage · ${item.usedTokens} tokens · Context capacity unavailable`
|
|
43
|
+
: `Context Usage · ${item.usedTokens}/${item.contextWindowTokens} tokens`,
|
|
42
44
|
role: 'heading',
|
|
43
45
|
};
|
|
44
46
|
}
|
package/dist/cli-runtime.d.ts
CHANGED
|
@@ -232,6 +232,7 @@ export declare function resolveInteractiveProviderStartup(options: {
|
|
|
232
232
|
}): Promise<{
|
|
233
233
|
effectiveModel: string | undefined;
|
|
234
234
|
trustProjectRequestAvailable: boolean;
|
|
235
|
+
contextWindowTokensForModel: (modelId?: string) => number | undefined;
|
|
235
236
|
}>;
|
|
236
237
|
export declare function createDefaultDependencies(entrypoint?: string): CliDependencies;
|
|
237
238
|
export declare function createBackgroundWorkerRuntime(workerSink: RuntimeEventSink, dispatch: {
|
package/dist/cli-runtime.js
CHANGED
|
@@ -51,7 +51,7 @@ import { FallbackModelProvider } from './providers/fallback-provider.js';
|
|
|
51
51
|
import { ProviderCredentialVault } from './persistence/provider-credential-vault.js';
|
|
52
52
|
import { parseContextEnvironment, parseProviderEnvironment, } from './providers/environment.js';
|
|
53
53
|
import { ProviderAuthenticationError, resolveProviderCredential, } from './providers/provider-auth.js';
|
|
54
|
-
import { resolveProviderRegistry } from './providers/provider-registry.js';
|
|
54
|
+
import { resolveProviderContextWindowTokens, resolveProviderRegistry, } from './providers/provider-registry.js';
|
|
55
55
|
import { ProviderSettingsError, resolveProviderTarget, } from './providers/provider-settings.js';
|
|
56
56
|
import { ModelPricingRegistry, usageCostUsd } from './core/usage.js';
|
|
57
57
|
import { LocalToolRegistry } from './tools/local-tools.js';
|
|
@@ -2482,8 +2482,9 @@ export async function resolveInteractiveProviderStartup(options) {
|
|
|
2482
2482
|
truthyEnvironmentValue(environment.CLAUDE_CODE_SIMPLE);
|
|
2483
2483
|
if (disabled) {
|
|
2484
2484
|
let effectiveModel;
|
|
2485
|
+
let protocol;
|
|
2485
2486
|
try {
|
|
2486
|
-
|
|
2487
|
+
const target = await resolveProviderTarget({
|
|
2487
2488
|
configRoot: options.configRoot,
|
|
2488
2489
|
cwd: options.cwd,
|
|
2489
2490
|
environment,
|
|
@@ -2497,16 +2498,28 @@ export async function resolveInteractiveProviderStartup(options) {
|
|
|
2497
2498
|
? {}
|
|
2498
2499
|
: { profile: options.controls.providerProfile }),
|
|
2499
2500
|
includeSettings: false,
|
|
2500
|
-
})
|
|
2501
|
+
});
|
|
2502
|
+
effectiveModel = target.modelId;
|
|
2503
|
+
protocol = target.protocol;
|
|
2501
2504
|
}
|
|
2502
2505
|
catch (error) {
|
|
2503
2506
|
if (!(error instanceof ProviderSettingsError &&
|
|
2504
2507
|
error.code === 'model_required'))
|
|
2505
2508
|
throw error;
|
|
2506
2509
|
}
|
|
2510
|
+
const context = parseContextEnvironment(environment);
|
|
2507
2511
|
return {
|
|
2508
2512
|
effectiveModel,
|
|
2509
2513
|
trustProjectRequestAvailable: options.controls.trustProject,
|
|
2514
|
+
contextWindowTokensForModel: (modelId) => protocol === undefined
|
|
2515
|
+
? undefined
|
|
2516
|
+
: resolveProviderContextWindowTokens({
|
|
2517
|
+
protocol,
|
|
2518
|
+
modelId: modelId ?? effectiveModel ?? '',
|
|
2519
|
+
...(context.contextWindowTokens === undefined
|
|
2520
|
+
? {}
|
|
2521
|
+
: { explicitContextWindowTokens: context.contextWindowTokens }),
|
|
2522
|
+
}),
|
|
2510
2523
|
};
|
|
2511
2524
|
}
|
|
2512
2525
|
const assessment = await assessCurrentWorkspaceProviderSelection({
|
|
@@ -2531,8 +2544,9 @@ export async function resolveInteractiveProviderStartup(options) {
|
|
|
2531
2544
|
}
|
|
2532
2545
|
}
|
|
2533
2546
|
let effectiveModel;
|
|
2547
|
+
let protocol;
|
|
2534
2548
|
try {
|
|
2535
|
-
|
|
2549
|
+
const target = await resolveProviderTarget({
|
|
2536
2550
|
configRoot: options.configRoot,
|
|
2537
2551
|
cwd: options.cwd,
|
|
2538
2552
|
environment,
|
|
@@ -2547,13 +2561,28 @@ export async function resolveInteractiveProviderStartup(options) {
|
|
|
2547
2561
|
: { profile: options.controls.providerProfile }),
|
|
2548
2562
|
includeSettings: true,
|
|
2549
2563
|
includeProjectSettings,
|
|
2550
|
-
})
|
|
2564
|
+
});
|
|
2565
|
+
effectiveModel = target.modelId;
|
|
2566
|
+
protocol = target.protocol;
|
|
2551
2567
|
}
|
|
2552
2568
|
catch (error) {
|
|
2553
2569
|
if (!(error instanceof ProviderSettingsError && error.code === 'model_required'))
|
|
2554
2570
|
throw error;
|
|
2555
2571
|
}
|
|
2556
|
-
|
|
2572
|
+
const context = parseContextEnvironment(environment);
|
|
2573
|
+
return {
|
|
2574
|
+
effectiveModel,
|
|
2575
|
+
trustProjectRequestAvailable,
|
|
2576
|
+
contextWindowTokensForModel: (modelId) => protocol === undefined
|
|
2577
|
+
? undefined
|
|
2578
|
+
: resolveProviderContextWindowTokens({
|
|
2579
|
+
protocol,
|
|
2580
|
+
modelId: modelId ?? effectiveModel ?? '',
|
|
2581
|
+
...(context.contextWindowTokens === undefined
|
|
2582
|
+
? {}
|
|
2583
|
+
: { explicitContextWindowTokens: context.contextWindowTokens }),
|
|
2584
|
+
}),
|
|
2585
|
+
};
|
|
2557
2586
|
}
|
|
2558
2587
|
export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta.url)) {
|
|
2559
2588
|
const dependencies = {
|
|
@@ -2629,6 +2658,7 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
|
|
|
2629
2658
|
statePath: interactiveStatePath,
|
|
2630
2659
|
factory: {
|
|
2631
2660
|
createService: createInteractiveService,
|
|
2661
|
+
contextWindowTokens: startup.contextWindowTokensForModel,
|
|
2632
2662
|
scheduledPrompts: Boolean(effectiveModel),
|
|
2633
2663
|
},
|
|
2634
2664
|
...(signal ? { signal } : {}),
|
|
@@ -24,6 +24,12 @@ export function estimateTextTokens(value) {
|
|
|
24
24
|
}
|
|
25
25
|
return Math.ceil(ascii / 4) + nonAscii;
|
|
26
26
|
}
|
|
27
|
+
const IMAGE_VISUAL_TOKEN_ESTIMATE = 1_600;
|
|
28
|
+
// This provider-neutral conservative fallback never interprets billed usage;
|
|
29
|
+
// observed provider usage remains authoritative at a ContextBudget watermark.
|
|
30
|
+
function estimateImageTokens(mediaType) {
|
|
31
|
+
return 8 + estimateTextTokens(mediaType) + IMAGE_VISUAL_TOKEN_ESTIMATE;
|
|
32
|
+
}
|
|
27
33
|
function estimateMessageTokens(message) {
|
|
28
34
|
let tokens = 4 + estimateTextTokens(message.role);
|
|
29
35
|
if (message.role === 'tool') {
|
|
@@ -31,16 +37,14 @@ function estimateMessageTokens(message) {
|
|
|
31
37
|
estimateTextTokens(message.toolCallId) +
|
|
32
38
|
estimateTextTokens(message.content);
|
|
33
39
|
for (const image of message.images ?? []) {
|
|
34
|
-
tokens +=
|
|
35
|
-
8 + estimateTextTokens(image.mediaType) + estimateTextTokens(image.data);
|
|
40
|
+
tokens += estimateImageTokens(image.mediaType);
|
|
36
41
|
}
|
|
37
42
|
return tokens + (message.isError ? 1 : 0);
|
|
38
43
|
}
|
|
39
44
|
tokens += estimateTextTokens(message.content);
|
|
40
45
|
if (message.role === 'user') {
|
|
41
46
|
for (const image of message.images ?? []) {
|
|
42
|
-
tokens +=
|
|
43
|
-
8 + estimateTextTokens(image.mediaType) + estimateTextTokens(image.data);
|
|
47
|
+
tokens += estimateImageTokens(image.mediaType);
|
|
44
48
|
}
|
|
45
49
|
for (const document of message.documents ?? []) {
|
|
46
50
|
tokens += 8 + estimateTextTokens(document.mediaType) + 2000;
|
|
@@ -4,6 +4,10 @@ export interface AnthropicCompatibleProviderOptions {
|
|
|
4
4
|
baseUrl: string;
|
|
5
5
|
apiKey: string;
|
|
6
6
|
model: string;
|
|
7
|
+
promptCacheResolver?: (target: {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
model: string;
|
|
10
|
+
}) => AnthropicPromptCachePolicy;
|
|
7
11
|
maxOutputTokens?: number;
|
|
8
12
|
anthropicVersion?: string;
|
|
9
13
|
webSearch?: boolean;
|
|
@@ -32,6 +36,9 @@ export declare class AnthropicCompatibleProvider implements ModelProvider {
|
|
|
32
36
|
private readonly maxToolMetadataBytes;
|
|
33
37
|
private readonly maxErrorBodyBytes;
|
|
34
38
|
private readonly thinking;
|
|
39
|
+
private readonly wireModel;
|
|
40
|
+
private readonly supportsAdaptiveThinking;
|
|
41
|
+
private readonly providerBetas;
|
|
35
42
|
private readonly promptCaching;
|
|
36
43
|
private readonly streaming;
|
|
37
44
|
constructor(options: AnthropicCompatibleProviderOptions);
|
|
@@ -3,6 +3,7 @@ import { transportFailureKind } from './provider-errors.js';
|
|
|
3
3
|
import { reportProviderTransportActivity } from './provider-transport-activity.js';
|
|
4
4
|
import { markNonStreamingFallbackEligible } from './non-streaming-fallback-provider.js';
|
|
5
5
|
import { createAnthropicPromptCachePolicyResolver, } from './anthropic-prompt-cache.js';
|
|
6
|
+
import { resolveAnthropicModelSpec } from './anthropic-model-spec.js';
|
|
6
7
|
function isRecord(value) {
|
|
7
8
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
9
|
}
|
|
@@ -49,17 +50,23 @@ function positiveInteger(value, label) {
|
|
|
49
50
|
}
|
|
50
51
|
return value;
|
|
51
52
|
}
|
|
52
|
-
function validateThinking(thinking) {
|
|
53
|
+
function validateThinking(thinking, supportsAdaptiveThinking) {
|
|
53
54
|
if (!thinking)
|
|
54
55
|
return undefined;
|
|
55
56
|
if (!['enabled', 'adaptive', 'disabled'].includes(thinking.mode)) {
|
|
56
57
|
throw new Error(`Unsupported thinking mode: ${thinking.mode}`);
|
|
57
58
|
}
|
|
59
|
+
if (thinking.mode === 'adaptive' && !supportsAdaptiveThinking) {
|
|
60
|
+
throw new Error('Adaptive thinking is only supported for explicit Claude Sonnet 4.6 or Opus 4.6 models');
|
|
61
|
+
}
|
|
58
62
|
if (thinking.maxTokens !== undefined) {
|
|
59
63
|
positiveInteger(thinking.maxTokens, 'Max thinking tokens');
|
|
60
64
|
if (thinking.mode === 'disabled') {
|
|
61
65
|
throw new Error('Max thinking tokens cannot be used when thinking is disabled');
|
|
62
66
|
}
|
|
67
|
+
if (thinking.mode === 'adaptive') {
|
|
68
|
+
throw new Error('Max thinking tokens cannot be used with adaptive thinking; use enabled thinking for a fixed budget');
|
|
69
|
+
}
|
|
63
70
|
}
|
|
64
71
|
return thinking;
|
|
65
72
|
}
|
|
@@ -643,21 +650,26 @@ export class AnthropicCompatibleProvider {
|
|
|
643
650
|
maxToolMetadataBytes;
|
|
644
651
|
maxErrorBodyBytes;
|
|
645
652
|
thinking;
|
|
653
|
+
wireModel;
|
|
654
|
+
supportsAdaptiveThinking;
|
|
655
|
+
providerBetas;
|
|
646
656
|
promptCaching;
|
|
647
657
|
streaming;
|
|
648
658
|
constructor(options) {
|
|
649
659
|
this.options = options;
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
}
|
|
660
|
+
const modelSpec = resolveAnthropicModelSpec(options.model, options.contextWindowTokens);
|
|
661
|
+
positiveInteger(modelSpec.contextWindowTokens, 'Context window tokens');
|
|
653
662
|
this.endpoint = `${options.baseUrl.replace(/\/+$/, '')}/messages`;
|
|
654
|
-
this.model =
|
|
663
|
+
this.model = modelSpec.model;
|
|
664
|
+
this.wireModel = modelSpec.wireModel;
|
|
665
|
+
this.supportsAdaptiveThinking = modelSpec.supportsAdaptiveThinking;
|
|
666
|
+
this.providerBetas = Object.freeze([...modelSpec.betas]);
|
|
655
667
|
this.streaming = options.streaming ?? true;
|
|
656
668
|
this.fetchImplementation = options.fetchImplementation ?? fetch;
|
|
657
669
|
this.maxOutputTokens = positiveInteger(options.maxOutputTokens ??
|
|
658
|
-
(
|
|
670
|
+
(this.wireModel.includes('claude-opus-4-6')
|
|
659
671
|
? 64_000
|
|
660
|
-
:
|
|
672
|
+
: this.wireModel.startsWith('claude-')
|
|
661
673
|
? 32_000
|
|
662
674
|
: 8192), 'Max output tokens');
|
|
663
675
|
this.capabilities = {
|
|
@@ -668,22 +680,25 @@ export class AnthropicCompatibleProvider {
|
|
|
668
680
|
documents: true,
|
|
669
681
|
webSearch: options.webSearch === true,
|
|
670
682
|
thinking: {
|
|
671
|
-
modes:
|
|
683
|
+
modes: modelSpec.supportsAdaptiveThinking
|
|
684
|
+
? ['enabled', 'adaptive', 'disabled']
|
|
685
|
+
: ['enabled', 'disabled'],
|
|
672
686
|
maxTokens: true,
|
|
673
687
|
},
|
|
674
|
-
|
|
675
|
-
? {}
|
|
676
|
-
: { contextWindowTokens: options.contextWindowTokens }),
|
|
688
|
+
contextWindowTokens: modelSpec.contextWindowTokens,
|
|
677
689
|
maxOutputTokens: this.maxOutputTokens,
|
|
678
690
|
terminalReasons: true,
|
|
679
691
|
};
|
|
680
|
-
this.thinking = validateThinking(options.thinking);
|
|
681
|
-
const promptCaching = options.promptCaching
|
|
682
|
-
?
|
|
683
|
-
: (options.
|
|
692
|
+
this.thinking = validateThinking(options.thinking, this.supportsAdaptiveThinking);
|
|
693
|
+
const promptCaching = options.promptCaching !== undefined
|
|
694
|
+
? options.promptCaching
|
|
695
|
+
: (options.promptCacheResolver?.({
|
|
696
|
+
baseUrl: options.baseUrl,
|
|
697
|
+
model: this.wireModel,
|
|
698
|
+
}) ??
|
|
684
699
|
createAnthropicPromptCachePolicyResolver({}, 'native')({
|
|
685
700
|
baseUrl: options.baseUrl,
|
|
686
|
-
model:
|
|
701
|
+
model: this.wireModel,
|
|
687
702
|
}));
|
|
688
703
|
this.promptCaching = promptCaching
|
|
689
704
|
? cacheControl(promptCaching.ttl)
|
|
@@ -703,17 +718,20 @@ export class AnthropicCompatibleProvider {
|
|
|
703
718
|
if (request.webSearch && request.tools?.length) {
|
|
704
719
|
throw new Error('Web search cannot be combined with model tools');
|
|
705
720
|
}
|
|
706
|
-
const thinking = validateThinking(request.thinking ?? this.thinking);
|
|
721
|
+
const thinking = validateThinking(request.thinking ?? this.thinking, this.supportsAdaptiveThinking);
|
|
707
722
|
const maxTokens = Math.max(this.maxOutputTokens, thinking?.maxTokens === undefined ? 0 : thinking.maxTokens + 1);
|
|
708
723
|
const thinkingPayload = thinking === undefined
|
|
709
724
|
? undefined
|
|
710
725
|
: thinking.mode === 'disabled'
|
|
711
726
|
? { type: 'disabled' }
|
|
712
|
-
:
|
|
713
|
-
type: '
|
|
714
|
-
|
|
715
|
-
|
|
727
|
+
: thinking.mode === 'adaptive'
|
|
728
|
+
? { type: 'adaptive' }
|
|
729
|
+
: {
|
|
730
|
+
type: 'enabled',
|
|
731
|
+
budget_tokens: thinking.maxTokens ?? maxTokens - 1,
|
|
732
|
+
};
|
|
716
733
|
const betas = [
|
|
734
|
+
...this.providerBetas,
|
|
717
735
|
...(request.betas ?? []),
|
|
718
736
|
...(thinking && thinking.mode !== 'disabled'
|
|
719
737
|
? ['interleaved-thinking-2025-05-14']
|
|
@@ -730,7 +748,7 @@ export class AnthropicCompatibleProvider {
|
|
|
730
748
|
...(betas.length ? { 'anthropic-beta': betas.join(',') } : {}),
|
|
731
749
|
},
|
|
732
750
|
body: JSON.stringify({
|
|
733
|
-
model: this.
|
|
751
|
+
model: this.wireModel,
|
|
734
752
|
max_tokens: maxTokens,
|
|
735
753
|
messages: serialized.messages,
|
|
736
754
|
stream: this.streaming,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const ANTHROPIC_LONG_CONTEXT_BETA = "context-1m-2025-08-07";
|
|
2
|
+
export interface ResolvedAnthropicModelSpec {
|
|
3
|
+
readonly model: string;
|
|
4
|
+
readonly wireModel: string;
|
|
5
|
+
readonly contextWindowTokens: number;
|
|
6
|
+
readonly betas: readonly string[];
|
|
7
|
+
readonly supportsAdaptiveThinking: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveAnthropicModelSpec(model: string, explicitContextWindowTokens?: number): ResolvedAnthropicModelSpec;
|
|
10
|
+
//# sourceMappingURL=anthropic-model-spec.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const ANTHROPIC_LONG_CONTEXT_BETA = 'context-1m-2025-08-07';
|
|
2
|
+
export function resolveAnthropicModelSpec(model, explicitContextWindowTokens) {
|
|
3
|
+
const longContext = model.endsWith('[1m]');
|
|
4
|
+
const wireModel = longContext ? model.slice(0, -'[1m]'.length) : model;
|
|
5
|
+
if (longContext && wireModel.trim().length === 0) {
|
|
6
|
+
throw new Error('Anthropic [1m] model spec must include a base model name');
|
|
7
|
+
}
|
|
8
|
+
return Object.freeze({
|
|
9
|
+
model,
|
|
10
|
+
wireModel,
|
|
11
|
+
contextWindowTokens: explicitContextWindowTokens ?? (longContext ? 1_000_000 : 200_000),
|
|
12
|
+
betas: Object.freeze(longContext ? [ANTHROPIC_LONG_CONTEXT_BETA] : []),
|
|
13
|
+
supportsAdaptiveThinking: wireModel === 'claude-sonnet-4-6' || wireModel === 'claude-opus-4-6',
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=anthropic-model-spec.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ModelProvider, ModelThinkingConfig } from '../core/runtime.js';
|
|
2
2
|
import { type CodexOAuthVault } from './codex-oauth.js';
|
|
3
|
-
import { type ProviderTarget } from './provider-settings.js';
|
|
3
|
+
import { type ProviderProtocol, type ProviderTarget } from './provider-settings.js';
|
|
4
4
|
import type { ProviderCredentialSourceMetadata, ProviderCredentialReader, ResolvedProviderCredential } from './provider-auth.js';
|
|
5
5
|
import { parseProviderEnvironment, type ContextEnvironment } from './environment.js';
|
|
6
6
|
import { type AnthropicPromptCachePolicy } from './anthropic-prompt-cache.js';
|
|
@@ -47,6 +47,11 @@ export interface ProviderRegistry {
|
|
|
47
47
|
readonly credentialSource: ProviderRegistrySourceMetadata;
|
|
48
48
|
create(modelId?: string): ModelProvider;
|
|
49
49
|
}
|
|
50
|
+
export declare function resolveProviderContextWindowTokens(options: {
|
|
51
|
+
protocol: ProviderProtocol;
|
|
52
|
+
modelId: string;
|
|
53
|
+
explicitContextWindowTokens?: number;
|
|
54
|
+
}): number | undefined;
|
|
50
55
|
export declare function resolveProviderRegistry(options: ResolveProviderRegistryOptions): Promise<ProviderRegistry>;
|
|
51
56
|
export declare function createProviderRegistry(options: ProviderRegistryOptions): ProviderRegistry;
|
|
52
57
|
//# sourceMappingURL=provider-registry.d.ts.map
|
|
@@ -6,6 +6,7 @@ import { DeadlineModelProvider } from './deadline-provider.js';
|
|
|
6
6
|
import { NonStreamingFallbackModelProvider } from './non-streaming-fallback-provider.js';
|
|
7
7
|
import { CodexOAuthCredentialManager, } from './codex-oauth.js';
|
|
8
8
|
import { resolveProviderTarget, } from './provider-settings.js';
|
|
9
|
+
import { resolveAnthropicModelSpec } from './anthropic-model-spec.js';
|
|
9
10
|
import { ProviderAuthenticationError, resolveProviderCredential, } from './provider-auth.js';
|
|
10
11
|
import { parseContextEnvironment, parseProviderEnvironment, } from './environment.js';
|
|
11
12
|
import { createAnthropicPromptCachePolicyResolver, } from './anthropic-prompt-cache.js';
|
|
@@ -17,6 +18,14 @@ export class ProviderRegistryError extends Error {
|
|
|
17
18
|
this.code = code;
|
|
18
19
|
}
|
|
19
20
|
}
|
|
21
|
+
export function resolveProviderContextWindowTokens(options) {
|
|
22
|
+
if (options.protocol === 'anthropic-messages')
|
|
23
|
+
return resolveAnthropicModelSpec(options.modelId, options.explicitContextWindowTokens).contextWindowTokens;
|
|
24
|
+
if (options.protocol === 'openai-compatible' ||
|
|
25
|
+
options.protocol === 'openai-responses')
|
|
26
|
+
return options.explicitContextWindowTokens;
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
20
29
|
export async function resolveProviderRegistry(options) {
|
|
21
30
|
const environment = options.environment ?? process.env;
|
|
22
31
|
const target = await resolveProviderTarget({
|
|
@@ -175,10 +184,7 @@ class NativeProviderRegistry {
|
|
|
175
184
|
...(this.options.anthropicPromptCacheResolver === undefined
|
|
176
185
|
? {}
|
|
177
186
|
: {
|
|
178
|
-
|
|
179
|
-
baseUrl: target.baseUrl,
|
|
180
|
-
model: target.modelId,
|
|
181
|
-
}),
|
|
187
|
+
promptCacheResolver: this.options.anthropicPromptCacheResolver,
|
|
182
188
|
}),
|
|
183
189
|
...(this.options.providerEnvironment?.maxOutputTokens === undefined
|
|
184
190
|
? {}
|