diff --git a/.compliance/config.yml b/.compliance/config.yml index bf5b9139..74e5f53e 100644 --- a/.compliance/config.yml +++ b/.compliance/config.yml @@ -15,7 +15,7 @@ asvs_level: L2 soc2_categories: [security, confidentiality] severity_threshold_to_block: critical llm_provider: bedrock -llm_model: eu.anthropic.claude-sonnet-4-6 +llm_model: eu.anthropic.claude-sonnet-5 # Add suppressions here as triage progresses. Each rule needs a justification # and (for ISO/SOC 2 controls) a risk_id pointing at a Risk Register entry. diff --git a/.github/workflows/compliance-swarm.yml b/.github/workflows/compliance-swarm.yml index 8a1082a0..fdb39b81 100644 --- a/.github/workflows/compliance-swarm.yml +++ b/.github/workflows/compliance-swarm.yml @@ -54,4 +54,4 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # Override default model if desired: - # COMPLIANCE_BEDROCK_MODEL: 'eu.anthropic.claude-sonnet-4-6' + # COMPLIANCE_BEDROCK_MODEL: 'eu.anthropic.claude-sonnet-5' diff --git a/.github/workflows/pr-agent.yml b/.github/workflows/pr-agent.yml index e07ce2d5..2ff01e60 100644 --- a/.github/workflows/pr-agent.yml +++ b/.github/workflows/pr-agent.yml @@ -61,16 +61,17 @@ jobs: AWS_REGION_NAME: ${{ secrets.PR_AGENT_AWS_REGION }} AWS_REGION: ${{ secrets.PR_AGENT_AWS_REGION }} - # ── Model: Claude Opus 4.8 via the EU Bedrock inference profile, with - # Sonnet 4.6 as fallback if the Opus profile isn't enabled for these - # creds. custom_model_max_tokens is required because these ids are not - # in PR-Agent's built-in token map. - CONFIG.MODEL: "bedrock/eu.anthropic.claude-opus-4-8" - CONFIG.MODEL_WEAK: "bedrock/eu.anthropic.claude-sonnet-4-6" - CONFIG.FALLBACK_MODELS: '["bedrock/eu.anthropic.claude-sonnet-4-6"]' + # ── Model: Claude Sonnet 5 via the EU Bedrock inference profile. The + # strong and weak slots both point at it: this account has no larger + # model enabled, so a second id would only be the same model under + # another name. No FALLBACK_MODELS for the same reason: a fallback + # list naming the primary is not a fallback. custom_model_max_tokens + # is required because this id is not in PR-Agent's built-in map. + CONFIG.MODEL: "bedrock/eu.anthropic.claude-sonnet-5" + CONFIG.MODEL_WEAK: "bedrock/eu.anthropic.claude-sonnet-5" CONFIG.CUSTOM_MODEL_MAX_TOKENS: "200000" # Input window PR-Agent prunes the diff to fit. Default (~32k) truncated - # large PRs; raise it so the whole diff is reviewed (Sonnet 4.6 = 200k ctx). + # large PRs; raise it so the whole diff is reviewed (Sonnet 5 = 1M ctx). CONFIG.MAX_MODEL_TOKENS: "64000" LITELLM.DROP_PARAMS: "true" diff --git a/.github/workflows/swedish-compliance-review.yml b/.github/workflows/swedish-compliance-review.yml index d41f5e4a..9fae7762 100644 --- a/.github/workflows/swedish-compliance-review.yml +++ b/.github/workflows/swedish-compliance-review.yml @@ -79,7 +79,7 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_REGION: ${{ secrets.AWS_REGION || 'eu-north-1' }} - REVIEW_MODEL: eu.anthropic.claude-sonnet-4-6 + REVIEW_MODEL: eu.anthropic.claude-sonnet-5 # Two-stage mode: read the diff from the artifact instead of git-diffing. DIFF_FILE: diff.patch FILES_FILE: files.txt diff --git a/extensions/general/document-extraction/index.ts b/extensions/general/document-extraction/index.ts index c1f99234..4217897c 100644 --- a/extensions/general/document-extraction/index.ts +++ b/extensions/general/document-extraction/index.ts @@ -170,7 +170,7 @@ async function extractAndPersist( return } extractedData = data as unknown as Record - model = process.env.BEDROCK_MODEL_ID || 'eu.anthropic.claude-sonnet-4-6' + model = process.env.BEDROCK_MODEL_ID || 'eu.anthropic.claude-sonnet-5' } catch (err) { log.warn('extraction threw', { doc: document.id, diff --git a/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts b/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts index 2de640ae..eb7ded11 100644 --- a/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts +++ b/extensions/general/invoice-inbox/lib/extract-invoice-fields.ts @@ -20,9 +20,9 @@ const log = createLogger('invoice-inbox-extract') // Both overridable via env vars so ops can swap models / raise token caps // without a code deploy. Defaults match what's expected to be set in -// production (eu.anthropic.claude-sonnet-4-6 in eu-north-1, 8192 tokens: +// production (eu.anthropic.claude-sonnet-5 in eu-north-1, 8192 tokens: // enough headroom for invoices with 20+ line items). -const MODEL = process.env.BEDROCK_MODEL_ID || 'eu.anthropic.claude-sonnet-4-6' +const MODEL = process.env.BEDROCK_MODEL_ID || 'eu.anthropic.claude-sonnet-5' const MAX_TOKENS = (() => { const parsed = Number(process.env.BEDROCK_MAX_TOKENS) // Use the env value only if it's a positive number: `||` would also diff --git a/lib/agent/chat/__tests__/run-turn-memory.test.ts b/lib/agent/chat/__tests__/run-turn-memory.test.ts index 001f6dd2..a677cacb 100644 --- a/lib/agent/chat/__tests__/run-turn-memory.test.ts +++ b/lib/agent/chat/__tests__/run-turn-memory.test.ts @@ -21,7 +21,10 @@ vi.mock('@/lib/agent/composer/client', () => ({ }, }, }), - SONNET_MODEL: 'claude-sonnet-4-6', + SONNET_MODEL: 'claude-sonnet-5', + MAX_TOKENS_NO_THINKING: 5400, + MAX_TOKENS_STANDARD: 16000, + MAX_TOKENS_DEEP: 24000, })) // system-prompt builder: return a minimal valid shape. @@ -66,7 +69,7 @@ function makeIntent(): AgentIntent { sheetTitle: 'x', atoms: { mode: 'progressive', horizontal: [], includeCompanyVertical: false, includeCompanyModifiers: false }, tools: ['gnubok_remember_fact'], - model: 'claude-sonnet-4-6', + model: 'claude-sonnet-5', capture: async () => ({}), promptTemplate: () => '', } diff --git a/lib/agent/chat/__tests__/run-turn-thinking.test.ts b/lib/agent/chat/__tests__/run-turn-thinking.test.ts index b403956a..4055714c 100644 --- a/lib/agent/chat/__tests__/run-turn-thinking.test.ts +++ b/lib/agent/chat/__tests__/run-turn-thinking.test.ts @@ -18,9 +18,16 @@ vi.mock('@/lib/agent/composer/client', () => ({ }, }, }), - SONNET_MODEL: 'claude-sonnet-4-6', + SONNET_MODEL: 'claude-sonnet-5', + MAX_TOKENS_NO_THINKING: 5400, + MAX_TOKENS_STANDARD: 16000, + MAX_TOKENS_DEEP: 24000, })) +const MAX_TOKENS_NO_THINKING = 5400 +const MAX_TOKENS_STANDARD = 16000 +const MAX_TOKENS_DEEP = 24000 + vi.mock('../system-prompt', () => ({ buildSystemPrompt: vi.fn().mockResolvedValue({ blocks: [], @@ -84,7 +91,11 @@ async function runWith(intent: AgentIntent) { emit: () => true, }) // The args object the stream was invoked with. - return messagesCreate.mock.calls[0][0] as { thinking?: unknown; max_tokens?: number } + return messagesCreate.mock.calls[0][0] as { + thinking?: unknown + output_config?: unknown + max_tokens?: number + } } beforeEach(() => { @@ -92,17 +103,33 @@ beforeEach(() => { }) describe('runChatTurn: extended thinking wiring', () => { - it('passes a thinking config and bumps max_tokens when the intent opts in', async () => { - const args = await runWith({ ...baseIntent(), thinking: { budgetTokens: 2000 } }) - expect(args.thinking).toEqual({ type: 'enabled', budget_tokens: 2000 }) - // budget must be strictly below max_tokens: we add the normal output budget. - expect(args.max_tokens).toBe(2000 + 4096) + it('requests adaptive thinking with a summarized display when the intent opts in', async () => { + const args = await runWith({ ...baseIntent(), thinking: { effort: 'high' } }) + // Sonnet 5 rejects { type: 'enabled', budget_tokens } outright. + expect(args.thinking).toEqual({ type: 'adaptive', display: 'summarized' }) + // display:'summarized' is what makes reasoning text actually arrive; the + // default 'omitted' streams empty thinking blocks and the chat's + // "Tänker …" block would never populate. + expect(args.output_config).toEqual({ effort: 'high' }) + expect(args.max_tokens).toBe(MAX_TOKENS_STANDARD) }) - it('omits thinking and keeps the default budget when the intent does not opt in', async () => { + it('raises the output ceiling for the deep-reasoning tier', async () => { + const args = await runWith({ ...baseIntent(), thinking: { effort: 'xhigh' } }) + expect(args.output_config).toEqual({ effort: 'xhigh' }) + // max_tokens now caps thinking AND the visible reply together. + expect(args.max_tokens).toBe(MAX_TOKENS_DEEP) + }) + + it('omits thinking and effort when the intent does not opt in', async () => { const args = await runWith(baseIntent()) expect(args.thinking).toBeUndefined() - expect(args.max_tokens).toBe(4096) + expect(args.output_config).toBeUndefined() + // A non-thinking intent keeps its own reply-sized ceiling: max_tokens now + // covers thinking too, so inheriting the reasoning tier's 16000 would let a + // plain answer run several times longer than it ever did before. + expect(args.max_tokens).toBe(MAX_TOKENS_NO_THINKING) + expect(args.max_tokens).toBeLessThan(MAX_TOKENS_STANDARD) }) }) diff --git a/lib/agent/chat/__tests__/system-prompt.test.ts b/lib/agent/chat/__tests__/system-prompt.test.ts index 03207070..1dbfc52f 100644 --- a/lib/agent/chat/__tests__/system-prompt.test.ts +++ b/lib/agent/chat/__tests__/system-prompt.test.ts @@ -23,7 +23,7 @@ const intent: AgentIntent = { sheetTitle: 'Fråga din assistent', atoms: { mode: 'progressive', horizontal: [], includeCompanyVertical: false, includeCompanyModifiers: false }, tools: [], - model: 'claude-sonnet-4-6', + model: 'claude-sonnet-5', capture: async () => ({}), promptTemplate: () => '', } diff --git a/lib/agent/chat/run-turn.ts b/lib/agent/chat/run-turn.ts index d045a39a..c49f3358 100644 --- a/lib/agent/chat/run-turn.ts +++ b/lib/agent/chat/run-turn.ts @@ -1,5 +1,11 @@ import type { SupabaseClient } from '@supabase/supabase-js' -import { getAnthropic, SONNET_MODEL } from '@/lib/agent/composer/client' +import { + getAnthropic, + MAX_TOKENS_DEEP, + MAX_TOKENS_NO_THINKING, + MAX_TOKENS_STANDARD, + SONNET_MODEL, +} from '@/lib/agent/composer/client' import type { AgentIntent } from '@/lib/agent/intents/types' import { agentToolRegistry } from '@/lib/agent/tools/registry' import type { AgentTool, AgentActorContext, StagedOperationResult } from '@/lib/agent/tools/types' @@ -242,13 +248,28 @@ export async function runChatTurn(args: RunTurnArgs): Promise { // Extended thinking ("tänka längre"): when the intent opts in, every model // call in the loop gets a reasoning channel so the agent reasons BEFORE it // answers or commits to a tool, instead of narrating its steps in the - // visible reply. budget_tokens must be ≥ 1024 and strictly below max_tokens, - // so the normal 4096 output budget is added on top. The reasoning streams to - // the client as reasoning_delta and renders in a collapsible "Tänkte…" block. + // visible reply. The reasoning streams to the client as reasoning_delta and + // renders in a collapsible "Tänkte…" block. + // + // display:'summarized' is load-bearing, not cosmetic. The default is + // 'omitted', which still emits thinking blocks but with empty text: measured + // on this account at xhigh effort, summarized returned ~1k characters of + // reasoning and the default returned none. Without it the collapsible + // "Tänker …" block in the chat would silently never populate. + // + // max_tokens now covers thinking and the reply together, so the ceiling + // follows what the intent opted into. An intent with no thinking keeps its + // reply-sized cap: giving it the reasoning tier's headroom would let a plain + // answer run four times longer for no reason. const thinking = intent.thinking - ? { type: 'enabled' as const, budget_tokens: intent.thinking.budgetTokens } + ? { type: 'adaptive' as const, display: 'summarized' as const } : undefined - const maxTokens = (intent.thinking?.budgetTokens ?? 0) + 4096 + const outputConfig = intent.thinking ? { effort: intent.thinking.effort } : undefined + const maxTokens = !intent.thinking + ? MAX_TOKENS_NO_THINKING + : intent.thinking.effort === 'xhigh' || intent.thinking.effort === 'max' + ? MAX_TOKENS_DEEP + : MAX_TOKENS_STANDARD // 4 + 5 + 6: iterate until the model stops requesting tools. while (iterations < MAX_TOOL_ITERATIONS) { @@ -266,6 +287,7 @@ export async function runChatTurn(args: RunTurnArgs): Promise { messages, tools: tools.length > 0 ? tools.map(toAnthropicTool) : undefined, ...(thinking ? { thinking } : {}), + ...(outputConfig ? { output_config: outputConfig } : {}), }) stream.on('text', (delta) => { diff --git a/lib/agent/composer/client.ts b/lib/agent/composer/client.ts index 71ffff1b..eb8e01e6 100644 --- a/lib/agent/composer/client.ts +++ b/lib/agent/composer/client.ts @@ -59,27 +59,41 @@ export function getAnthropic(): AnthropicBedrock { return cached } -// Bedrock model IDs. Region prefix `eu.` keeps inference inside eu-north-1. -// Both are env-overridable so ops can swap models without a code deploy. +// Bedrock model IDs. Region prefix `eu.` keeps inference inside eu-north-1 +// (a bare `anthropic.claude-sonnet-5` is rejected: on-demand throughput needs +// the cross-region inference profile). Both are env-overridable so ops can +// swap models without a code deploy. // -// Per plan §14 the composer's atom-selection call should run on Opus 4.7 for -// the higher-stakes selection reasoning. Opus 4.7 is not yet enabled on this -// AWS Bedrock account (403 "not available for this account": request access -// on the AWS console under Bedrock → Model access). For now we point OPUS at -// Sonnet 4.6 so the composer still works; atom selection on Sonnet is still -// good: it's a structured-output call via tool_use forcing, not deep -// reasoning. Flip BEDROCK_OPUS_MODEL_ID back to eu.anthropic.claude-opus-4-7 -// once Opus access lands. -export const OPUS_MODEL = process.env.BEDROCK_OPUS_MODEL_ID || 'eu.anthropic.claude-sonnet-4-6' -export const SONNET_MODEL = process.env.BEDROCK_SONNET_MODEL_ID || 'eu.anthropic.claude-sonnet-4-6' +// Both point at Sonnet 5, verified enabled on this Bedrock account. The two +// names are kept because the intents split on them: OPUS_MODEL marks the +// heavy-reasoning intents (supplier-invoice review, VAT review, bokslut) so +// that split survives if a genuinely larger model is enabled here later. +export const OPUS_MODEL = process.env.BEDROCK_OPUS_MODEL_ID || 'eu.anthropic.claude-sonnet-5' +export const SONNET_MODEL = process.env.BEDROCK_SONNET_MODEL_ID || 'eu.anthropic.claude-sonnet-5' -// Extended-thinking budgets (budget_tokens) for the chat intents. These are -// ceilings, not floors: the model spends only what a turn needs, so a generous -// cap improves hard turns (multi-source VAT synthesis, anomaly detection) -// without taxing simple ones. run-turn derives max_tokens = budget + 4096, so -// raising these is safe: no manual max_tokens bookkeeping. Tiered to match the -// model split: DEEP for the Opus / heavy-reasoning intents, STANDARD for the -// rest. Early-stage default favours reasoning quality over token cost; dial -// down here in one place if latency/cost ever bites. -export const THINKING_BUDGET_STANDARD = 6000 -export const THINKING_BUDGET_DEEP = 12000 +// Reasoning depth for the chat intents. +// +// Sonnet 5 removed the fixed thinking budget: `thinking: {type:'enabled', +// budget_tokens}` is rejected outright ("not supported for this model. Use +// thinking.type.adaptive and output_config.effort"). Depth is now a qualitative +// effort level and the model spends what a turn actually needs. +// +// Levels are `low | medium | high | xhigh | max`. Measured on this account: +// at `medium` a multi-step Swedish VAT question produced no reasoning at all, +// at `xhigh` it produced ~1k characters. Since the point of enabling thinking +// on these intents is that the agent reasons BEFORE it answers rather than +// narrating in the reply, DEEP uses xhigh and STANDARD high. +export const EFFORT_STANDARD = 'high' as const +export const EFFORT_DEEP = 'xhigh' as const + +// Output ceilings per tier. Previously derived as budget + 4096; with no budget +// to derive from these are explicit, and there are now three of them because +// max_tokens caps thinking AND the visible reply together: an intent that does +// not think must not inherit a ceiling sized for one that does. +// +// NO_THINKING is the old 4096 reply cap scaled by ~30% for Sonnet 5's new +// tokenizer, which spends that much more on the same Swedish text, so the +// effective reply length is unchanged rather than quietly cut. +export const MAX_TOKENS_NO_THINKING = 5400 +export const MAX_TOKENS_STANDARD = 16000 +export const MAX_TOKENS_DEEP = 24000 diff --git a/lib/agent/intents/general-help.ts b/lib/agent/intents/general-help.ts index 75974506..3b9cab7b 100644 --- a/lib/agent/intents/general-help.ts +++ b/lib/agent/intents/general-help.ts @@ -1,5 +1,5 @@ import { defineAgentIntent } from './types' -import { SONNET_MODEL, THINKING_BUDGET_STANDARD } from '@/lib/agent/composer/client' +import { SONNET_MODEL, EFFORT_STANDARD } from '@/lib/agent/composer/client' import { renderAgentGroundRules } from './shared-rules' // general.help: always-present "Fråga min assistent" from the top nav. @@ -98,7 +98,7 @@ export const generalHelp = defineAgentIntent ({ route: route ?? null }), diff --git a/lib/agent/intents/inbox-bulk-book.ts b/lib/agent/intents/inbox-bulk-book.ts index 6c34fbb5..17e15954 100644 --- a/lib/agent/intents/inbox-bulk-book.ts +++ b/lib/agent/intents/inbox-bulk-book.ts @@ -1,5 +1,5 @@ import { defineAgentIntent } from './types' -import { SONNET_MODEL, THINKING_BUDGET_STANDARD } from '@/lib/agent/composer/client' +import { SONNET_MODEL, EFFORT_STANDARD } from '@/lib/agent/composer/client' // inbox.bulk-book: "Fråga assistenten" on a multi-selection in the Underlag // view (Dokumentinkorgen). Unlike transaction.categorization (which keys off the @@ -116,7 +116,7 @@ export const inboxBulkBook = defineAgentIntent { const ids = Array.isArray(item_ids) ? item_ids.filter((x): x is string => typeof x === 'string') : [] diff --git a/lib/agent/intents/invoice-draft.ts b/lib/agent/intents/invoice-draft.ts index 4f087689..c5b91777 100644 --- a/lib/agent/intents/invoice-draft.ts +++ b/lib/agent/intents/invoice-draft.ts @@ -1,5 +1,5 @@ import { defineAgentIntent } from './types' -import { SONNET_MODEL, THINKING_BUDGET_STANDARD } from '@/lib/agent/composer/client' +import { SONNET_MODEL, EFFORT_STANDARD } from '@/lib/agent/composer/client' import { renderAgentGroundRules } from './shared-rules' // invoice.draft: "Fråga om denna faktura" from the invoice form. @@ -83,7 +83,7 @@ export const invoiceDraft = defineAgentIntent { // Resolve the effective customer_id. When the FAB lands here from diff --git a/lib/agent/intents/supplier-invoice-review.ts b/lib/agent/intents/supplier-invoice-review.ts index 952876c1..7155f436 100644 --- a/lib/agent/intents/supplier-invoice-review.ts +++ b/lib/agent/intents/supplier-invoice-review.ts @@ -1,5 +1,5 @@ import { defineAgentIntent } from './types' -import { OPUS_MODEL, THINKING_BUDGET_DEEP } from '@/lib/agent/composer/client' +import { OPUS_MODEL, EFFORT_DEEP } from '@/lib/agent/composer/client' import { renderAgentGroundRules } from './shared-rules' // supplier_invoice.review: "Fråga din assistent" from a supplier invoice @@ -171,7 +171,7 @@ export const supplierInvoiceReview = defineAgentIntent< // so the visible reply is a single conclusion after the booking is staged: // not a pre-tool analysis echoed again post-tool. Matches the always-on // prompt's promise that reasoning happens in the (separately shown) tankekanal. - thinking: { budgetTokens: THINKING_BUDGET_DEEP }, + thinking: { effort: EFFORT_DEEP }, capture: async ({ supplier_invoice_id }, { supabase, companyId }) => { const { data: invoice } = await supabase diff --git a/lib/agent/intents/transaction-categorization.ts b/lib/agent/intents/transaction-categorization.ts index f5f0ae3c..cb01568d 100644 --- a/lib/agent/intents/transaction-categorization.ts +++ b/lib/agent/intents/transaction-categorization.ts @@ -1,5 +1,5 @@ import { defineAgentIntent } from './types' -import { SONNET_MODEL, THINKING_BUDGET_STANDARD } from '@/lib/agent/composer/client' +import { SONNET_MODEL, EFFORT_STANDARD } from '@/lib/agent/composer/client' // transaction.categorization: "Fråga om denna transaktion" on a transaction // row. @@ -78,7 +78,7 @@ export const transactionCategorization = defineAgentIntent< // Reason before proposing: read underlag + history and work out the VAT // treatment in the thinking channel, so the visible reply is one short // motivation, not a play-by-play of each tool call. - thinking: { budgetTokens: THINKING_BUDGET_STANDARD }, + thinking: { effort: EFFORT_STANDARD }, capture: async ({ transaction_id }, { supabase, companyId }) => { const { data: tx } = await supabase diff --git a/lib/agent/intents/types.ts b/lib/agent/intents/types.ts index f805200e..f4cf9245 100644 --- a/lib/agent/intents/types.ts +++ b/lib/agent/intents/types.ts @@ -36,11 +36,13 @@ export interface AgentIntent, Captured = unknown> // override to Opus. model: string - // Extended-thinking budget. When set, run-turn enables a reasoning channel - // (thinking: { type: 'enabled', budget_tokens }) on every model call in the - // loop, so the agent reasons before it answers instead of narrating its - // steps in the visible reply. Omit to disable. budget_tokens must be ≥ 1024. - thinking?: { budgetTokens: number } + // Reasoning depth. When set, run-turn enables adaptive thinking on every + // model call in the loop, so the agent reasons before it answers instead of + // narrating its steps in the visible reply. Omit to disable. + // + // Sonnet 5 rejects the old fixed budget outright, so this is an effort level + // (EFFORT_STANDARD / EFFORT_DEEP), not a token count. + thinking?: { effort: 'low' | 'medium' | 'high' | 'xhigh' | 'max' } // Captures the page-context object the prompt template needs. Runs server- // side after the user clicks the button. Failures bubble up to the route. diff --git a/lib/agent/intents/vat-review.ts b/lib/agent/intents/vat-review.ts index 833202e9..bc58a32c 100644 --- a/lib/agent/intents/vat-review.ts +++ b/lib/agent/intents/vat-review.ts @@ -1,5 +1,5 @@ import { defineAgentIntent } from './types' -import { OPUS_MODEL, THINKING_BUDGET_DEEP } from '@/lib/agent/composer/client' +import { OPUS_MODEL, EFFORT_DEEP } from '@/lib/agent/composer/client' import { renderAgentGroundRules } from './shared-rules' // vat.review: "Fråga [namn]" from the VAT declaration preview. @@ -62,7 +62,7 @@ export const vatReview = defineAgentIntent({ // Reason over the period figures + Rutor in the thinking channel, so the // visible reply is one conclusion, not a running commentary of each read // followed by a restated summary. Parity with the other reasoning intents. - thinking: { budgetTokens: THINKING_BUDGET_DEEP }, + thinking: { effort: EFFORT_DEEP }, capture: async ({ period_type, year, period }, { supabase, companyId }) => { const { data: settings } = await supabase diff --git a/lib/agent/intents/verifikation-draft.ts b/lib/agent/intents/verifikation-draft.ts index 113c4e63..3db470f3 100644 --- a/lib/agent/intents/verifikation-draft.ts +++ b/lib/agent/intents/verifikation-draft.ts @@ -1,5 +1,5 @@ import { defineAgentIntent } from './types' -import { SONNET_MODEL, THINKING_BUDGET_STANDARD } from '@/lib/agent/composer/client' +import { SONNET_MODEL, EFFORT_STANDARD } from '@/lib/agent/composer/client' import { renderAgentGroundRules } from './shared-rules' import { resolvePeriodStatusForDate } from '@/lib/core/bookkeeping/period-service' @@ -102,7 +102,7 @@ export const verifikationDraft = defineAgentIntent< // analysis before the tool call and a near-identical answer after it. The // always-on prompt promises "resonemang sker i tankekanalen"; without this // that channel doesn't exist and the reasoning spills into the visible reply. - thinking: { budgetTokens: THINKING_BUDGET_STANDARD }, + thinking: { effort: EFFORT_STANDARD }, capture: async ({ journal_entry_id, description }, { supabase, companyId }) => { let entry: CapturedVerifikationDraft['entry'] = null diff --git a/scripts/swedish-compliance-review.mjs b/scripts/swedish-compliance-review.mjs index 550a29cd..966e25f3 100644 --- a/scripts/swedish-compliance-review.mjs +++ b/scripts/swedish-compliance-review.mjs @@ -11,7 +11,7 @@ import path from 'node:path'; const SKILLS_DIR = '.claude/skills'; const ALWAYS_LOAD = 'swedish-accounting-compliance'; -const MODEL = process.env.REVIEW_MODEL || 'eu.anthropic.claude-sonnet-4-6'; +const MODEL = process.env.REVIEW_MODEL || 'eu.anthropic.claude-sonnet-5'; const MAX_DIFF_CHARS = 180_000; const OUTPUT_FILE = 'review.md'; const COMMENT_MARKER = '';