fix: surface resolved model limits (#1608)

Signed-off-by: ulofiai <monsterking@tutamail.com>
Co-authored-by: ulofiai <monsterking@tutamail.com>
This commit is contained in:
ulofiai
2026-08-20 16:17:36 -07:00
committed by GitHub
co-authored by ulofiai
parent f3f2789f0a
commit 2ca5fb4027
3 changed files with 142 additions and 0 deletions
+30
View File
@@ -82,6 +82,35 @@ async function createPromptConfig(
return createMultiBlockMessage();
}
type ModelUsageSummary = Record<
string,
{
contextWindow: number;
maxOutputTokens: number;
}
>;
/**
* Keep resolved model limits visible without exposing token usage or cost details.
*/
function sanitizeModelUsage(
modelUsage: SDKResultMessage["modelUsage"] | undefined,
): ModelUsageSummary | undefined {
if (!modelUsage) {
return undefined;
}
return Object.fromEntries(
Object.entries(modelUsage).map(([model, usage]) => [
model,
{
contextWindow: usage.contextWindow,
maxOutputTokens: usage.maxOutputTokens,
},
]),
);
}
/**
* Sanitizes SDK output to match CLI sanitization behavior
*/
@@ -119,6 +148,7 @@ function sanitizeSdkOutput(
num_turns: resultMsg.num_turns,
total_cost_usd: resultMsg.total_cost_usd,
permission_denials_count: resultMsg.permission_denials?.length ?? 0,
modelUsage: sanitizeModelUsage(resultMsg.modelUsage),
},
null,
2,
+89
View File
@@ -64,6 +64,95 @@ describe("runClaudeWithSdk", () => {
}
});
test("logs resolved model limits without exposing token usage", async () => {
const consoleLogSpy = spyOn(console, "log").mockImplementation(() => {});
tempDir = await mkdtemp(join(tmpdir(), "claude-sdk-"));
process.env.RUNNER_TEMP = tempDir;
const promptPath = join(tempDir, "prompt.txt");
await writeFile(promptPath, "test prompt");
const initMessage = {
type: "system",
subtype: "init",
session_id: "session-123",
model: "claude-opus-5",
};
const resultMessage = {
type: "result",
subtype: "success",
is_error: false,
duration_ms: 434,
num_turns: 1,
total_cost_usd: 1.23,
permission_denials: [],
modelUsage: {
"claude-opus-5": {
inputTokens: 96209,
outputTokens: 55324,
cacheReadInputTokens: 1135701,
cacheCreationInputTokens: 149043,
webSearchRequests: 0,
costUSD: 1.23,
contextWindow: 200000,
maxOutputTokens: 64000,
},
},
};
mock.module("@anthropic-ai/claude-agent-sdk", () => ({
query: async function* () {
yield initMessage;
yield resultMessage;
},
}));
try {
const { runClaudeWithSdk } = await import("../src/run-claude-sdk");
await expect(
runClaudeWithSdk(promptPath, {
sdkOptions: {},
showFullOutput: false,
hasJsonSchema: false,
}),
).resolves.toMatchObject({ conclusion: "success" });
const sanitizedResult = consoleLogSpy.mock.calls
.map(([message]) => message)
.find(
(message) =>
typeof message === "string" && message.includes('"type": "result"'),
);
expect(sanitizedResult).toBeDefined();
if (typeof sanitizedResult !== "string") {
throw new Error("Sanitized result output was not logged");
}
expect(JSON.parse(sanitizedResult)).toEqual({
type: "result",
subtype: "success",
is_error: false,
duration_ms: 434,
num_turns: 1,
total_cost_usd: 1.23,
permission_denials_count: 0,
modelUsage: {
"claude-opus-5": {
contextWindow: 200000,
maxOutputTokens: 64000,
},
},
});
expect(sanitizedResult).not.toContain("inputTokens");
expect(sanitizedResult).not.toContain("costUSD");
} finally {
consoleLogSpy.mockRestore();
}
});
test("fails when result subtype is success but is_error is true", async () => {
const consoleErrorSpy = spyOn(console, "error").mockImplementation(
() => {},
+23
View File
@@ -275,6 +275,29 @@ For provider-specific models:
# ... other inputs
```
### 1M context models through an API gateway
When `ANTHROPIC_BASE_URL` points to an Anthropic-compatible API gateway,
Claude Code may not be able to verify that the gateway supports a model's native
1M context window and can budget the session at 200K instead. Append the
`[1m]` selector to explicitly use the 1M context window for supported models,
including Claude Opus 5 and Claude Sonnet 5:
```yaml
- uses: anthropics/claude-code-action@v1
with:
claude_args: |
--model "claude-opus-5[1m]"
# ... other inputs
```
Use the same selector when setting a model through `ANTHROPIC_MODEL` or another
Claude Code model environment variable. The selector is resolved by Claude Code
before requests are sent to the provider. The action's sanitized result output
includes each model's resolved
`contextWindow` and `maxOutputTokens` under `modelUsage`, so these limits are
visible without enabling `show_full_output`.
## Claude Code Settings
You can provide Claude Code settings to customize behavior such as model selection, environment variables, permissions, and hooks. Settings can be provided either as a JSON string or a path to a settings file.