testronaut 1.4.1 → 1.5.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/README.md +1 -1
- package/bin/init.js +21 -21
- package/bin/initHelpers.js +3 -12
- package/core/turnLoop.js +3 -1
- package/llm/modelResolver.js +4 -3
- package/llm/openAI/models.js +32 -0
- package/llm/openAI/openaiProvider.js +13 -2
- package/package.json +1 -1
- package/tests/binTests/initHelpersTests/models.test.js +11 -0
- package/tests/coreTests/turnLoop.test.js +1 -0
- package/tests/llmTests/modelResolver.test.js +4 -4
- package/tests/llmTests/openAIProvider.test.js +18 -0
- package/tests/toolsTests/tokenControl.test.js +19 -0
- package/todo.md +17 -0
- package/tools/tokenControl.js +27 -1
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ Current supported providers:
|
|
|
134
134
|
|
|
135
135
|
| Provider | Example Models |
|
|
136
136
|
|-----------|----------------|
|
|
137
|
-
| **OpenAI** | gpt-
|
|
137
|
+
| **OpenAI** | gpt-5.6, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, and legacy GPT/o-series models |
|
|
138
138
|
| **Google Gemini** | gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-8b |
|
|
139
139
|
|
|
140
140
|
More providers coming soon (Anthropic, Mistral, etc.).
|
package/bin/init.js
CHANGED
|
@@ -32,10 +32,10 @@ import {
|
|
|
32
32
|
migrateLegacyConfig,
|
|
33
33
|
defaultConfig,
|
|
34
34
|
makeEnvTemplate,
|
|
35
|
-
openAIModels,
|
|
36
35
|
geminiModels,
|
|
37
36
|
pickInitialIndex
|
|
38
37
|
} from './initHelpers.js';
|
|
38
|
+
import { DEFAULT_OPENAI_MODEL, OPENAI_MODELS } from '../llm/openAI/models.js';
|
|
39
39
|
|
|
40
40
|
export async function initializeTestronautProject() {
|
|
41
41
|
// ─────────────────────────────────────────────
|
|
@@ -81,36 +81,36 @@ export async function initializeTestronautProject() {
|
|
|
81
81
|
|
|
82
82
|
// 2b) Provider-specific model selection
|
|
83
83
|
if (llmProvider === 'openai') {
|
|
84
|
-
const
|
|
84
|
+
const currentModels = OPENAI_MODELS.filter(model => !model.legacy);
|
|
85
|
+
const legacyModels = OPENAI_MODELS.filter(model => model.legacy);
|
|
86
|
+
const choices = [
|
|
87
|
+
...currentModels.map(model => ({
|
|
88
|
+
title: `${model.label} (${model.description})`,
|
|
89
|
+
value: model.id,
|
|
90
|
+
})),
|
|
91
|
+
{ title: '── Legacy models ──', disabled: true },
|
|
92
|
+
...legacyModels.map(model => ({
|
|
93
|
+
title: `${model.label} (${model.description})`,
|
|
94
|
+
value: model.id,
|
|
95
|
+
})),
|
|
96
|
+
];
|
|
97
|
+
const selectedIndex = choices.findIndex(choice => choice.value === config.model);
|
|
98
|
+
const defaultIndex = choices.findIndex(choice => choice.value === DEFAULT_OPENAI_MODEL);
|
|
85
99
|
const { openaiModel } = await prompts({
|
|
86
100
|
type: 'select',
|
|
87
101
|
name: 'openaiModel',
|
|
88
102
|
message: 'Select an OpenAI model for agentic workflows (function/tool calling):',
|
|
89
|
-
choices
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
{ title: 'GPT-4o (multimodal, tool use)', value: 'gpt-4o' },
|
|
93
|
-
{ title: 'GPT-4o mini (speed/cost optimized)', value: 'gpt-4o-mini' },
|
|
94
|
-
{ title: 'o3 (reasoning w/ native tool use)', value: 'o3' },
|
|
95
|
-
{ title: 'o4-mini (reasoning, cost-effective)', value: 'o4-mini' },
|
|
96
|
-
// GPT-5 family (availability varies)
|
|
97
|
-
{ title: 'GPT-5 (previous reasoning model)', value: 'gpt-5' },
|
|
98
|
-
{ title: 'GPT-5 mini (faster)', value: 'gpt-5-mini' },
|
|
99
|
-
{ title: 'GPT-5 nano (lightweight)', value: 'gpt-5-nano' },
|
|
100
|
-
{ title: 'GPT-5.1 (latest reasoning model)', value: 'gpt-5.1' },
|
|
101
|
-
],
|
|
102
|
-
// Keep prior selection highlighted if present; fallback to 4.1-mini.
|
|
103
|
-
initial: pickInitialIndex(models, config.model, 'gpt-4.1-mini')
|
|
103
|
+
choices,
|
|
104
|
+
// Keep a prior selection highlighted; otherwise default to GPT-5.6.
|
|
105
|
+
initial: selectedIndex >= 0 ? selectedIndex : defaultIndex,
|
|
104
106
|
});
|
|
105
107
|
|
|
106
108
|
config.model = openaiModel;
|
|
107
109
|
|
|
108
|
-
//
|
|
110
|
+
// API access and rate limits vary by OpenAI account and project.
|
|
109
111
|
if (openaiModel?.startsWith('gpt-5')) {
|
|
110
112
|
console.log(`
|
|
111
|
-
|
|
112
|
-
- Access depends on your OpenAI account/region
|
|
113
|
-
- Some capabilities may be gated or rate-limited
|
|
113
|
+
ℹ️ GPT-5 availability and rate limits depend on your OpenAI account and project.
|
|
114
114
|
`);
|
|
115
115
|
}
|
|
116
116
|
} else if (llmProvider === 'gemini') {
|
package/bin/initHelpers.js
CHANGED
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* const models = openAIModels();
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import { OPENAI_MODELS } from '../llm/openAI/models.js';
|
|
29
|
+
|
|
28
30
|
/**
|
|
29
31
|
* Ensures backward compatibility for older configs where
|
|
30
32
|
* provider information may have been implied by the model field.
|
|
@@ -94,18 +96,7 @@ GEMINI_API_KEY=AIza...
|
|
|
94
96
|
* @returns {string[]} Array of OpenAI model names
|
|
95
97
|
*/
|
|
96
98
|
export function openAIModels() {
|
|
97
|
-
return
|
|
98
|
-
'gpt-4.1',
|
|
99
|
-
'gpt-4.1-mini',
|
|
100
|
-
'gpt-4o',
|
|
101
|
-
'gpt-4o-mini',
|
|
102
|
-
'o3',
|
|
103
|
-
'o4-mini',
|
|
104
|
-
'gpt-5',
|
|
105
|
-
'gpt-5-mini',
|
|
106
|
-
'gpt-5-nano',
|
|
107
|
-
'gpt-5.1',
|
|
108
|
-
];
|
|
99
|
+
return OPENAI_MODELS.map(({ id }) => id);
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
/**
|
package/core/turnLoop.js
CHANGED
|
@@ -41,7 +41,8 @@ import {
|
|
|
41
41
|
tokenUseCoolOff,
|
|
42
42
|
recordTokenUsage,
|
|
43
43
|
pruneOldTokenUsage,
|
|
44
|
-
updateLimitsFromHeaders
|
|
44
|
+
updateLimitsFromHeaders,
|
|
45
|
+
warnIfContextNearLimit
|
|
45
46
|
} from '../tools/tokenControl.js';
|
|
46
47
|
import { resolveProviderModel } from '../llm/modelResolver.js';
|
|
47
48
|
import { getLLM } from '../llm/llmFactory.js';
|
|
@@ -349,6 +350,7 @@ export const turnLoop = async (
|
|
|
349
350
|
// ─────────────────────────────────────────────
|
|
350
351
|
// STEP 2: Request next reasoning turn from model
|
|
351
352
|
// ─────────────────────────────────────────────
|
|
353
|
+
await warnIfContextNearLimit(MODEL_ID, { messages, tools: activeToolsSchema });
|
|
352
354
|
const { message, usage, headers } = await llm.chat({
|
|
353
355
|
model: MODEL_ID,
|
|
354
356
|
messages,
|
package/llm/modelResolver.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 1) Env: TESTRONAUT_PROVIDER / TESTRONAUT_MODEL
|
|
10
10
|
* 2) Config file (provider + model)
|
|
11
11
|
* 3) Legacy config (model === "openai", no provider)
|
|
12
|
-
* 4) Default: { provider: "openai", model: "gpt-
|
|
12
|
+
* 4) Default: { provider: "openai", model: "gpt-5.6" }
|
|
13
13
|
*
|
|
14
14
|
* Related tests: tests/llmTests/modelResolver.test.js
|
|
15
15
|
* Used by: core/turnLoop.js, llm/llmFactory.js
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import path from 'path';
|
|
19
19
|
import fs from 'fs';
|
|
20
|
+
import { DEFAULT_OPENAI_MODEL } from './openAI/models.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Resolve provider & model, honoring env overrides and safe defaults.
|
|
@@ -50,7 +51,7 @@ export function resolveProviderModel(opts = {}) {
|
|
|
50
51
|
|
|
51
52
|
// Legacy: model === "openai" and no provider
|
|
52
53
|
if (!cfg?.provider && cfg?.model === 'openai') {
|
|
53
|
-
return { provider: 'openai', model: envModel ||
|
|
54
|
+
return { provider: 'openai', model: envModel || DEFAULT_OPENAI_MODEL };
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
} catch (e) {
|
|
@@ -60,6 +61,6 @@ export function resolveProviderModel(opts = {}) {
|
|
|
60
61
|
// 3) Defaults
|
|
61
62
|
return {
|
|
62
63
|
provider: envProvider || 'openai',
|
|
63
|
-
model: envModel ||
|
|
64
|
+
model: envModel || DEFAULT_OPENAI_MODEL,
|
|
64
65
|
};
|
|
65
66
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported OpenAI model aliases and the capabilities Testronaut relies on.
|
|
3
|
+
* Dated snapshots and specialized Chat, Codex, Pro, and Cyber models are
|
|
4
|
+
* intentionally excluded from the interactive catalog.
|
|
5
|
+
*/
|
|
6
|
+
export const OPENAI_MODELS = [
|
|
7
|
+
{ id: 'gpt-5.6', label: 'GPT-5.6 Sol', description: 'flagship capability', contextWindow: 1050000, chatToolReasoningEffort: 'none' },
|
|
8
|
+
{ id: 'gpt-5.6-terra', label: 'GPT-5.6 Terra', description: 'balanced intelligence and cost', contextWindow: 1050000, chatToolReasoningEffort: 'none' },
|
|
9
|
+
{ id: 'gpt-5.6-luna', label: 'GPT-5.6 Luna', description: 'cost-sensitive, high-volume workloads', contextWindow: 1050000, chatToolReasoningEffort: 'none' },
|
|
10
|
+
{ id: 'gpt-5.5', label: 'GPT-5.5', description: 'previous flagship model', contextWindow: 1050000 },
|
|
11
|
+
|
|
12
|
+
{ id: 'gpt-5.4', label: 'GPT-5.4', description: 'earlier professional model', contextWindow: 1050000, legacy: true },
|
|
13
|
+
{ id: 'gpt-5.4-mini', label: 'GPT-5.4 mini', description: 'earlier balanced model', contextWindow: 400000, legacy: true },
|
|
14
|
+
{ id: 'gpt-5.4-nano', label: 'GPT-5.4 nano', description: 'earlier high-volume model', contextWindow: 400000, legacy: true },
|
|
15
|
+
{ id: 'gpt-5.2', label: 'GPT-5.2', description: 'previous frontier model', contextWindow: 400000, legacy: true },
|
|
16
|
+
{ id: 'gpt-5.1', label: 'GPT-5.1', description: 'legacy reasoning model', legacy: true },
|
|
17
|
+
{ id: 'gpt-5', label: 'GPT-5', description: 'legacy reasoning model', legacy: true },
|
|
18
|
+
{ id: 'gpt-5-mini', label: 'GPT-5 mini', description: 'legacy faster model', legacy: true },
|
|
19
|
+
{ id: 'gpt-5-nano', label: 'GPT-5 nano', description: 'legacy lightweight model', legacy: true },
|
|
20
|
+
{ id: 'gpt-4.1', label: 'GPT-4.1', description: 'legacy general-purpose model', legacy: true },
|
|
21
|
+
{ id: 'gpt-4.1-mini', label: 'GPT-4.1 mini', description: 'legacy faster model', legacy: true },
|
|
22
|
+
{ id: 'gpt-4o', label: 'GPT-4o', description: 'legacy multimodal model', legacy: true },
|
|
23
|
+
{ id: 'gpt-4o-mini', label: 'GPT-4o mini', description: 'legacy cost-optimized model', legacy: true },
|
|
24
|
+
{ id: 'o3', label: 'o3', description: 'legacy reasoning model', legacy: true },
|
|
25
|
+
{ id: 'o4-mini', label: 'o4-mini', description: 'legacy cost-effective reasoning model', legacy: true },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
export const DEFAULT_OPENAI_MODEL = 'gpt-5.6';
|
|
29
|
+
|
|
30
|
+
export function getOpenAIModel(modelId) {
|
|
31
|
+
return OPENAI_MODELS.find(({ id }) => id === modelId);
|
|
32
|
+
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import OpenAI from 'openai';
|
|
23
|
+
import { getOpenAIModel } from './models.js';
|
|
23
24
|
|
|
24
25
|
export class OpenAIProvider {
|
|
25
26
|
constructor({ apiKey } = {}) {
|
|
@@ -33,11 +34,21 @@ export class OpenAIProvider {
|
|
|
33
34
|
* @returns {Promise<{message:any, usage?:{total_tokens?:number, providerRaw?:any}, headers?:any}>}
|
|
34
35
|
*/
|
|
35
36
|
async chat({ model, messages, tools }) {
|
|
36
|
-
const
|
|
37
|
+
const request = {
|
|
37
38
|
model,
|
|
38
39
|
messages,
|
|
39
40
|
tools,
|
|
40
|
-
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// GPT-5.6 defaults to medium reasoning, but Chat Completions does not
|
|
44
|
+
// support combining reasoning with function tools. Responses API support
|
|
45
|
+
// is tracked separately; keep the current tool loop compatible meanwhile.
|
|
46
|
+
const chatToolReasoningEffort = getOpenAIModel(model)?.chatToolReasoningEffort;
|
|
47
|
+
if (tools?.length && chatToolReasoningEffort) {
|
|
48
|
+
request.reasoning_effort = chatToolReasoningEffort;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const res = await this.client.chat.completions.create(request);
|
|
41
52
|
|
|
42
53
|
// OpenAI already returns an OpenAI-like message and usage structure.
|
|
43
54
|
const message = res.choices?.[0]?.message ?? { role: 'assistant', content: '' };
|
package/package.json
CHANGED
|
@@ -4,6 +4,17 @@ import { openAIModels, geminiModels, isKnownModel, pickInitialIndex } from '../.
|
|
|
4
4
|
describe('model helpers', () => {
|
|
5
5
|
it('lists known models', () => {
|
|
6
6
|
expect(openAIModels()).toContain('gpt-4o');
|
|
7
|
+
expect(openAIModels()).toEqual(expect.arrayContaining([
|
|
8
|
+
'gpt-5.2',
|
|
9
|
+
'gpt-5.4',
|
|
10
|
+
'gpt-5.4-mini',
|
|
11
|
+
'gpt-5.4-nano',
|
|
12
|
+
'gpt-5.5',
|
|
13
|
+
'gpt-5.6',
|
|
14
|
+
'gpt-5.6-terra',
|
|
15
|
+
'gpt-5.6-luna',
|
|
16
|
+
]));
|
|
17
|
+
expect(openAIModels()).not.toContain('gpt-5.3');
|
|
7
18
|
expect(geminiModels()).toContain('gemini-2.5-flash');
|
|
8
19
|
});
|
|
9
20
|
|
|
@@ -67,6 +67,7 @@ vi.mock('../../tools/tokenControl.js', () => ({
|
|
|
67
67
|
totalTokensUsed: turnTimestamps.reduce((a, [, t]) => a + t, 0),
|
|
68
68
|
})),
|
|
69
69
|
updateLimitsFromHeaders: vi.fn(() => {}),
|
|
70
|
+
warnIfContextNearLimit: vi.fn(async () => ({ warned: false })),
|
|
70
71
|
}));
|
|
71
72
|
|
|
72
73
|
vi.mock('../../tools/toolSchema.js', () => ({
|
|
@@ -62,13 +62,13 @@ describe('resolveProviderModel', () => {
|
|
|
62
62
|
writeConfig(temp, { model: 'openai' });
|
|
63
63
|
|
|
64
64
|
const res = resolveProviderModel({ cwd: temp });
|
|
65
|
-
expect(res).toEqual({ provider: 'openai', model: 'gpt-
|
|
65
|
+
expect(res).toEqual({ provider: 'openai', model: 'gpt-5.6' });
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it('returns defaults when config missing', () => {
|
|
69
69
|
const temp = makeTempProject();
|
|
70
70
|
const res = resolveProviderModel({ cwd: temp });
|
|
71
|
-
expect(res).toEqual({ provider: 'openai', model: 'gpt-
|
|
71
|
+
expect(res).toEqual({ provider: 'openai', model: 'gpt-5.6' });
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it('returns defaults when JSON parsing fails', () => {
|
|
@@ -77,7 +77,7 @@ describe('resolveProviderModel', () => {
|
|
|
77
77
|
|
|
78
78
|
const res = resolveProviderModel({ cwd: temp });
|
|
79
79
|
expect(res.provider).toBe('openai');
|
|
80
|
-
expect(res.model).toBe('gpt-
|
|
80
|
+
expect(res.model).toBe('gpt-5.6');
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
it('trims env vars and treats empty strings as absent', () => {
|
|
@@ -90,7 +90,7 @@ describe('resolveProviderModel', () => {
|
|
|
90
90
|
process.env.TESTRONAUT_MODEL = ' ';
|
|
91
91
|
res = resolveProviderModel();
|
|
92
92
|
// falls back to defaults when env vars are just whitespace
|
|
93
|
-
expect(res).toEqual({ provider: 'openai', model: 'gpt-
|
|
93
|
+
expect(res).toEqual({ provider: 'openai', model: 'gpt-5.6' });
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
it('prefers config when only provider is in env and model only in config', () => {
|
|
@@ -78,6 +78,24 @@ describe('OpenAIProvider', () => {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
it.each(['gpt-5.6', 'gpt-5.6-terra', 'gpt-5.6-luna'])(
|
|
82
|
+
'disables reasoning for %s function tools on Chat Completions',
|
|
83
|
+
async (model) => {
|
|
84
|
+
const prov = new OpenAIProvider({ apiKey: 'sk-abc' });
|
|
85
|
+
const tools = [{ type: 'function', function: { name: 'doThing', parameters: { type: 'object' } } }];
|
|
86
|
+
|
|
87
|
+
await prov.chat({ model, messages: [{ role: 'user', content: 'go' }], tools });
|
|
88
|
+
|
|
89
|
+
expect(shared.lastCreateArgs).toMatchObject({ model, tools, reasoning_effort: 'none' });
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
it('does not add a reasoning override to GPT-5.6 requests without tools', async () => {
|
|
94
|
+
const prov = new OpenAIProvider({ apiKey: 'sk-abc' });
|
|
95
|
+
await prov.chat({ model: 'gpt-5.6', messages: [] });
|
|
96
|
+
expect(shared.lastCreateArgs).not.toHaveProperty('reasoning_effort');
|
|
97
|
+
});
|
|
98
|
+
|
|
81
99
|
it('returns normalized message, usage, and headers', async () => {
|
|
82
100
|
const prov = new OpenAIProvider({ apiKey: 'sk-abc' });
|
|
83
101
|
const { message, usage, headers } = await prov.chat({
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
tokenUseCoolOff,
|
|
34
34
|
recordTokenUsage,
|
|
35
35
|
pruneOldTokenUsage,
|
|
36
|
+
warnIfContextNearLimit,
|
|
36
37
|
__resetTokenControlForTests,
|
|
37
38
|
} from '../../tools/tokenControl.js';
|
|
38
39
|
|
|
@@ -111,6 +112,24 @@ describe('tokenControl', () => {
|
|
|
111
112
|
// sanity check it actually changed (unless defaults already 1234)
|
|
112
113
|
if (before.tpm !== 1234) expect(after.tpm).not.toBe(before.tpm);
|
|
113
114
|
});
|
|
115
|
+
|
|
116
|
+
it('uses tier-1 defaults without letting generic GPT-5 rules shadow variants', () => {
|
|
117
|
+
expect(getCurrentTokenLimit('gpt-5.6').tpm).toBe(500000);
|
|
118
|
+
expect(getCurrentTokenLimit('gpt-5.6-luna').tpm).toBe(500000);
|
|
119
|
+
expect(getCurrentTokenLimit('gpt-5.4-nano').tpm).toBe(200000);
|
|
120
|
+
expect(getCurrentTokenLimit('gpt-5-mini').tpm).toBe(240000);
|
|
121
|
+
expect(getCurrentTokenLimit('gpt-5-nano').tpm).toBe(600000);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('warns without truncating when known context usage crosses the threshold', async () => {
|
|
126
|
+
tiktokenMocks.encoding_for_model_impl.mockReturnValue(encMock);
|
|
127
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
128
|
+
const result = await warnIfContextNearLimit('gpt-5.2', '1234567890', 0.00002);
|
|
129
|
+
expect(result.warned).toBe(true);
|
|
130
|
+
expect(result.contextWindow).toBe(400000);
|
|
131
|
+
expect(warn).toHaveBeenCalled();
|
|
132
|
+
warn.mockRestore();
|
|
114
133
|
});
|
|
115
134
|
|
|
116
135
|
describe('rolling window + cooldown', () => {
|
package/todo.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Testronaut CLI TODO
|
|
2
|
+
|
|
3
|
+
## OpenAI provider follow-ups
|
|
4
|
+
|
|
5
|
+
- Migrate the OpenAI adapter from Chat Completions to the Responses API in a
|
|
6
|
+
dedicated change. Preserve Testronaut's provider-neutral message and tool-call
|
|
7
|
+
contract, add response-ID/reasoning-state handling, normalize usage and raw
|
|
8
|
+
headers, and cover multi-turn tool continuation with unit and API smoke tests.
|
|
9
|
+
Once migrated, remove the GPT-5.6 Chat Completions compatibility override that
|
|
10
|
+
sets `reasoning_effort` to `none` for function-tool requests.
|
|
11
|
+
- Add active context budgeting after its behavior is designed and tested. Use
|
|
12
|
+
model context/output metadata to reserve response and reasoning capacity, then
|
|
13
|
+
apply explicit history compaction or summarization rules. Do not silently
|
|
14
|
+
truncate mission instructions, current DOM state, or unmatched tool calls.
|
|
15
|
+
- Learn effective OpenAI TPM/RPM limits from successful response headers and
|
|
16
|
+
rate-limit errors. Prefer observed project/model limits over inferring a named
|
|
17
|
+
account tier; retain the environment override and conservative startup values.
|
package/tools/tokenControl.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
import { encoding_for_model, get_encoding } from '@dqbd/tiktoken';
|
|
24
24
|
import { wait } from './turnLoopUtils.js';
|
|
25
|
+
import { getOpenAIModel } from '../llm/openAI/models.js';
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Dynamic token-per-minute limits by model family.
|
|
@@ -35,10 +36,16 @@ import { wait } from './turnLoopUtils.js';
|
|
|
35
36
|
*/
|
|
36
37
|
const DEFAULT_LIMITS = [
|
|
37
38
|
// ── OpenAI (newer first) ────────────────────────────────────────────────
|
|
39
|
+
{ test: /^gpt-5\.6-luna(-|$)/i, tpm: 500000 },
|
|
40
|
+
{ test: /^gpt-5\.6(-|$)/i, tpm: 500000 },
|
|
41
|
+
{ test: /^gpt-5\.5(-|$)/i, tpm: 500000 },
|
|
42
|
+
{ test: /^gpt-5\.4-nano(-|$)/i, tpm: 200000 },
|
|
43
|
+
{ test: /^gpt-5\.4(-|$)/i, tpm: 500000 },
|
|
44
|
+
{ test: /^gpt-5\.2(-|$)/i, tpm: 500000 },
|
|
38
45
|
{ test: /^gpt-5.1(-|$)/i, tpm: 120000 },
|
|
39
|
-
{ test: /^gpt-5(-|$)/i, tpm: 90000 },
|
|
40
46
|
{ test: /^gpt-5-mini(-|$)/i, tpm: 240000 },
|
|
41
47
|
{ test: /^gpt-5-nano(-|$)/i, tpm: 600000 },
|
|
48
|
+
{ test: /^gpt-5(-|$)/i, tpm: 90000 },
|
|
42
49
|
|
|
43
50
|
{ test: /^gpt-4o(-|$)/i, tpm: 450000 },
|
|
44
51
|
{ test: /^gpt-4\.1(-|$)/i, tpm:1000000 },
|
|
@@ -143,6 +150,25 @@ export const tokenEstimate = async (model, text) => {
|
|
|
143
150
|
return approx;
|
|
144
151
|
};
|
|
145
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Warn when a request is close to a known model context window. This is a
|
|
155
|
+
* diagnostic only: Testronaut does not truncate or compact content here.
|
|
156
|
+
*/
|
|
157
|
+
export async function warnIfContextNearLimit(model, payload, threshold = 0.9) {
|
|
158
|
+
const contextWindow = getOpenAIModel(model)?.contextWindow;
|
|
159
|
+
if (!contextWindow) return { warned: false };
|
|
160
|
+
|
|
161
|
+
const estimatedTokens = await tokenEstimate(model, payload);
|
|
162
|
+
const warned = estimatedTokens >= contextWindow * threshold;
|
|
163
|
+
if (warned) {
|
|
164
|
+
console.warn(
|
|
165
|
+
`⚠️ Estimated request context for ${model} is ${estimatedTokens}/${contextWindow} tokens. ` +
|
|
166
|
+
'Testronaut will send it unchanged; consider reducing mission or tool history.'
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
return { warned, estimatedTokens, contextWindow };
|
|
170
|
+
}
|
|
171
|
+
|
|
146
172
|
/* ---------------- Dynamic limit resolution ---------------- */
|
|
147
173
|
|
|
148
174
|
/**
|