From 19b6406cb72a047dedb5faf04d1754f795067b1f Mon Sep 17 00:00:00 2001 From: Ali Mohammad Date: Tue, 20 Oct 2026 00:08:34 +0300 Subject: [PATCH] V11: Cap output tokens in scaffolded agents Scaffolded agent.ts sets modelSettings { maxTokens: 4096 } so first runs do not fail on low-credit OpenRouter keys; README gets a troubleshooting entry. Co-Authored-By: Claude Opus 5.5 --- .../init/__snapshots__/templates.test.ts.snap | 84 +++++++++++++------ src/cli/init/templates.test.ts | 9 ++ src/cli/init/templates.ts | 15 +++- 3 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/cli/init/__snapshots__/templates.test.ts.snap b/src/cli/init/__snapshots__/templates.test.ts.snap index e6ecdcfb..e82d34a4 100644 --- a/src/cli/init/__snapshots__/templates.test.ts.snap +++ b/src/cli/init/__snapshots__/templates.test.ts.snap @@ -16,12 +16,15 @@ export const currentTime = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'anthropic/claude-sonnet-4-5', instructions, tools }; +const config: CreateAgentConfig = { model: 'anthropic/claude-sonnet-4-5', instructions, tools, modelSettings }; export default config; /** @@ -29,7 +32,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -66,12 +69,15 @@ export const wordCount = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime, rollDie, wordCount]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'anthropic/claude-sonnet-4-5', instructions, tools }; +const config: CreateAgentConfig = { model: 'anthropic/claude-sonnet-4-5', instructions, tools, modelSettings }; export default config; /** @@ -79,7 +85,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -100,12 +106,15 @@ export const currentTime = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'google/gemini-2.5-flash', instructions, tools }; +const config: CreateAgentConfig = { model: 'google/gemini-2.5-flash', instructions, tools, modelSettings }; export default config; /** @@ -113,7 +122,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -150,12 +159,15 @@ export const wordCount = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime, rollDie, wordCount]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'google/gemini-2.5-flash', instructions, tools }; +const config: CreateAgentConfig = { model: 'google/gemini-2.5-flash', instructions, tools, modelSettings }; export default config; /** @@ -163,7 +175,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -219,12 +231,15 @@ export const currentTime = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'ollama/llama3', instructions, tools }; +const config: CreateAgentConfig = { model: 'ollama/llama3', instructions, tools, modelSettings }; export default config; /** @@ -232,7 +247,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -269,12 +284,15 @@ export const wordCount = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime, rollDie, wordCount]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'ollama/llama3', instructions, tools }; +const config: CreateAgentConfig = { model: 'ollama/llama3', instructions, tools, modelSettings }; export default config; /** @@ -282,7 +300,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -303,12 +321,15 @@ export const currentTime = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'openai/gpt-4.1-mini', instructions, tools }; +const config: CreateAgentConfig = { model: 'openai/gpt-4.1-mini', instructions, tools, modelSettings }; export default config; /** @@ -316,7 +337,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -353,12 +374,15 @@ export const wordCount = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime, rollDie, wordCount]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'openai/gpt-4.1-mini', instructions, tools }; +const config: CreateAgentConfig = { model: 'openai/gpt-4.1-mini', instructions, tools, modelSettings }; export default config; /** @@ -366,7 +390,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -387,12 +411,15 @@ export const currentTime = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'openrouter/openai/gpt-4.1-mini', instructions, tools }; +const config: CreateAgentConfig = { model: 'openrouter/openai/gpt-4.1-mini', instructions, tools, modelSettings }; export default config; /** @@ -400,7 +427,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -437,12 +464,15 @@ export const wordCount = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime, rollDie, wordCount]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'openrouter/openai/gpt-4.1-mini', instructions, tools }; +const config: CreateAgentConfig = { model: 'openrouter/openai/gpt-4.1-mini', instructions, tools, modelSettings }; export default config; /** @@ -450,7 +480,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -471,12 +501,15 @@ export const currentTime = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'pi/openrouter/openai/gpt-4.1-mini', instructions, tools }; +const config: CreateAgentConfig = { model: 'pi/openrouter/openai/gpt-4.1-mini', instructions, tools, modelSettings }; export default config; /** @@ -484,7 +517,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; @@ -521,12 +554,15 @@ export const wordCount = defineTool({ const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [currentTime, rollDie, wordCount]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: 'pi/openrouter/openai/gpt-4.1-mini', instructions, tools }; +const config: CreateAgentConfig = { model: 'pi/openrouter/openai/gpt-4.1-mini', instructions, tools, modelSettings }; export default config; /** @@ -534,7 +570,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } " `; diff --git a/src/cli/init/templates.test.ts b/src/cli/init/templates.test.ts index aeda0a5a..515e23d8 100644 --- a/src/cli/init/templates.test.ts +++ b/src/cli/init/templates.test.ts @@ -197,6 +197,15 @@ describe('renderProject', () => { expect(files['README.md']).not.toMatch(/(^|\s)npm run/); }); + it.each(combos.filter(([, template]) => template !== 'yaml'))( + 'caps output tokens so a low-credit key does not fail its first run: %s / %s', + (provider, template) => { + const files = renderProject(config(provider, template)); + expect(files['src/agent.ts']).toMatch(/const modelSettings = \{ maxTokens: 4096 \};[\s\S]*tools, modelSettings \}/); + expect(files['README.md']).toContain('requires more credits'); + } + ); + it.each(TEMPLATES)('snapshot: %s package.json', (template) => { expect(renderProject(config('openai', template))['package.json']).toMatchSnapshot(); }); diff --git a/src/cli/init/templates.ts b/src/cli/init/templates.ts index e422cf5c..3c4e6515 100644 --- a/src/cli/init/templates.ts +++ b/src/cli/init/templates.ts @@ -155,12 +155,15 @@ ${TIME_TOOL}${extra} const instructions = 'You are a helpful assistant. Use your tools when they help.'; const tools = [${toolNames(config.template).join(', ')}]; +// Caps each reply so a first run works on a low-credit key (providers such as OpenRouter reserve the +// model's full output limit up front). Raise it when you need longer answers. +const modelSettings = { maxTokens: 4096 }; /** * The agent's options. The default export is what \`lousho dev src/agent.ts\` * (and \`lousho chat\`, \`mcp\`, \`acp\`, \`build\`) serve. */ -const config: CreateAgentConfig = { model: '${config.provider}/${info.defaultModel}', instructions, tools }; +const config: CreateAgentConfig = { model: '${config.provider}/${info.defaultModel}', instructions, tools, modelSettings }; export default config; /** @@ -168,7 +171,7 @@ export default config; * optional \`provider\` lets tests swap in a scripted model (see agent.test.ts). */ export function buildAgent(provider?: LLMProvider) { - return provider ? createAgent({ provider, instructions, tools }) : createAgent(config); + return provider ? createAgent({ provider, instructions, tools, modelSettings }) : createAgent(config); } `; } @@ -326,6 +329,12 @@ function run(pm: PackageManager, script: string): string { return `${pm} run ${script}`; } +const TROUBLESHOOTING = `## Troubleshooting + +- \`This request requires more credits, or fewer max_tokens\` (OpenRouter and other credit-metered providers): the account cannot afford the model's full output limit. \`src/agent.ts\` caps replies with \`modelSettings: { maxTokens: 4096 }\`; lower it, or add credits and raise it. + +`; + function readme(config: ProjectConfig): string { const pm = config.packageManager; const info = providerInfo(config.provider); @@ -356,7 +365,7 @@ Run \`${run(pm, 'doctor')}\` if something does not work: it checks Node, package ${files} - \`.env.example\` - the environment variable your provider needs -Docs: https://github.com/LinuxDevil/agent-sdk/blob/main/docs/quick-start.md +${yaml ? '' : TROUBLESHOOTING}Docs: https://github.com/LinuxDevil/agent-sdk/blob/main/docs/quick-start.md `; }