v0.8.24: security hardening, model capability advertisement, browser improvements - #7562
Merged
Conversation
* fix(knowledge): bound JSON/YAML chunker expansion JsonYamlChunker re-parsed and re-serialized document content with no expansion limit. ChunkBudget only counts emitted chunks, so every parse and full-object stringify ran before it could fire — a small aliased YAML source expands to tens of MB, and the same content was parsed twice because isStructuredData and chunkJsonYaml each parsed it. - Measure the parsed value with the shared measureYamlExpansion guard before anything materializes it, and skip parsing entirely when the source is already larger than the ceiling - Size the ceiling to the most text the chunker could ever emit (maxChunks x chunkSize), floored at 4MB and capped at what the YAML file parser itself permits, so documents that fit the budget chunk exactly as before - Replace isStructuredData + chunkJsonYaml with one chunkStructured entry point that parses once and returns null when the content is not structured, leaving chunker selection with the document processor * fix(knowledge): allow proportionate expansion in the chunker guard Comparing the expansion estimate against the output budget mixed two units: measureYamlExpansion charges a flat per-node allowance, so a document of many small values is charged several times its pretty-printed size and was rejected even though its chunks fit the budget — a flat array of a million booleans is charged ~22MB against ~8MB of real output. Allow the larger of two admissible expansions: one that fits the output budget, and one proportionate to the source. Alias expansion overshoots its source by orders of magnitude, so it is still rejected, while an ordinary large document is no longer charged for the estimator's conservatism. Neither allowance ever exceeds the file parser's own cap.
…-bomb check (#7526) `extractDocxText` rescues a parse failure by re-reading the package as a possibly-empty document, and that rescue handed the buffer to JSZip with no size guard — so a zip bomb, whose rejection is exactly what routes it there, got its `word/document.xml` read into a string uncapped. Guard the rescue the way every other JSZip call site in the file already does, outside the catch so the rejection propagates. `extractDocAssets` handed an attacker-supplied .pptx/.docx straight to JSZip and inflated every media entry into a retained Buffer, with only a compressed-size ceiling upstream. Apply the same shared guard the document parsers and `extractDocumentStyle` already apply to the same class of input. No new limits are introduced — both call sites now use the existing shared ceilings, so a file rejected here is one Sim's text-extraction path already rejects today. Claude-Session: https://claude.ai/code/session_01VmwJP8EmSo3KcMFoKjpd6y Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…fig (#7525) * fix(chat): bound deployed-chat callers and stop leaking chat gate config Two authorization/throttling defects on chat deployments. **Denial of wallet on POST /api/chat/[identifier].** A deployed chat resolves its execution principal from the workflow's workspace, so the plan rate bucket, the usage/credit check and the concurrency reservation all belong to the owner while the request belongs to whoever found the link. Nothing bounded the caller, and an abort refunds none of it. Both the per-IP and the per-deployment bucket now run after auth and before `preprocessExecution`, on every execution regardless of `authType` — an email or SSO visitor is still not the payer. `GET /api/chat/validate` answered for any anonymous caller, so `available:false` inventoried live deployments; it now needs a session and a per-user bucket. **Chat gate config exposed at workflow `read` on GET /api/workflows/[id]/chat/ status.** The route reimplemented the admin-gated detail projection inline, serving the `allowedEmails` allow-list, `hasPassword` and the customization blob to any workspace viewer, and asserting no `deploy.chat` capability. It is now an adapter over `chat_deployments.list` — the same operation `GET /api/v2/chat- deployments` binds — returning only the deployment's id and identifier, which is all the editor reads before fetching the detail from `/api/chat/manage/{id}`. The two buckets are the existing `enforceIpRateLimitWithIndependentBackstop` plus a new `enforceResourceRateLimit` beside its siblings in `route-helpers`. The IP bucket is consulted first and returns on refusal, so one flooding IP cannot drain the deployment's budget and 429 the real audience with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWh9WFMYNZ6uTFQFUzF8Bj * fix(chat): drop the env knobs and put the ceiling under the plan bucket Two corrections to the execution throttle. The per-deployment ceiling was 300/min, at or above the workspace `sync` counter it debits on every plan but enterprise — 50 free, 150 pro, 300 team. A flood therefore drained that shared counter, which the owner's API, webhook and scheduled runs draw from too, before the ceiling ever refused: the availability half of the report went unmitigated on exactly the plans most workspaces are on. It is now 60/min sustained, under even the cheapest paid plan, with a test that pins it there against `RATE_LIMITS`. The per-IP bucket drops to 30/min so one host cannot take a deployment's whole allowance, and both gain the 2x burst allowance the plan buckets already use. Both limits go back to plain constants. Every sibling deployment throttle — password, OTP, SSO, on chat and on public file shares — is a hardcoded `TokenBucketConfig`, so the two env vars were the only configurable ones of their kind and bought speculative tuning for a control with sane defaults. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWh9WFMYNZ6uTFQFUzF8Bj * fix(chat): derive the chat ceiling from the plan table it must stay under The 60/min ceiling still sat above the free plan's 50/min sync rate, so on free the shared counter — the one the owner's API, webhook and scheduled runs also draw from — still emptied before the ceiling refused. Every plan rate is also operator-overridable through `RATE_LIMIT_*_SYNC`, which no hardcoded number can track. It is now derived: 80% of the smallest configured plan sync rate, which is 40/min with the defaults and stays under every plan by construction. The per-IP bucket follows at half that. Tests assert the invariant against each plan in `RATE_LIMITS`, on burst as well as sustained rate, rather than pinning numbers that would need editing the next time a plan default moves. This floor is shared by all plans, so enterprise is held to the same 40/min as free. Sizing the slice to the payer's own plan needs the subscription, which `preprocessExecution` resolves just after this runs — that is the follow-up, and the same hook bounds the generic-webhook surface that is still unbounded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWh9WFMYNZ6uTFQFUzF8Bj * docs(chat): note the one plan rate where the derived ceiling lands equal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWh9WFMYNZ6uTFQFUzF8Bj * refactor(rate-limit): scope the per-IP bucket by resource id, not by bucket name The chat call interpolated the deployment id into `bucketName`, which produces a correct key but puts a per-deployment value into the field both log lines emit as `bucket` — high cardinality on a label meant to name a bucket family, and asymmetric with the `enforceResourceRateLimit` call beside it that takes the id as its own argument. `enforceIpRateLimitWithIndependentBackstop` now takes an optional `resourceId`, so the pair reads the same way and `resourceId` is logged as its own field. The unscoped key shape is unchanged for the existing callers, with a test pinning both shapes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWh9WFMYNZ6uTFQFUzF8Bj * test(rate-limit): drop needless any casts on the mock request createMockRequest already returns NextRequest, so the casts weakened the helper's input contract in the new tests for nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QWh9WFMYNZ6uTFQFUzF8Bj --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(models): gate forced tool use by model capability * fix(models): derive forced tool support from capability metadata * refactor(models): use catalog capabilities for forced tool support
* feat(editor): replace canonical mode toggle with icon switch * fix(emcn): avoid icon switch package import cycle
Revert a0f38db because the new icon switch is not ready to ship. Keep the restoration and remaining mode-toggle migrations in a separate follow-up.
Co-authored-by: Sim Pi Agent <pi@sim.ai>
* feat(browser): add verified form filling and horizontal scroll * fix(browser): align form schemas and activity titles * fix(browser): detect truncated popup and dialog observations
…dels (#7555) * fix(quickbooks): harden the app-level webhook ingress Three defects on the QuickBooks CloudEvents ingress: - The pre-ack path loaded and decrypted every account connected to the addressed Intuit app before checking the signature. Verifier tokens are now produced by an async generator and consumed one at a time, stopping at the first match, so a legitimate delivery no longer burns the whole app's fan-out inside Intuit's 3-second acknowledgement budget. - One unmodelled element rejected the entire delivery with 400. Intuit retries a 400 indefinitely and withholds later events until one is acknowledged, so a single bad payload stopped webhooks for every Sim workspace on that Intuit app. The array shape is still bounded, but elements are parsed individually, unparseable ones are dropped with a warning, and the delivery is acknowledged with 200. - formatInput emitted the lowercase wire token ("invoice") as entityType. It now resolves the trigger definition from the parsed entity and emits the canonical QuickBooks name ("Invoice") the read tools expect; eventType still carries the raw wire string. Also count an unroutable company id as ignored rather than failed, so a permanently impossible event no longer retries three times. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): stop item full updates from corrupting inventory and transaction history Two of these silently rewrite a customer's accounting records. An Item full update echoes the record read back from QuickBooks. Intuit documents InvStartDate asymmetrically: "For read operations, the date returned in this field is always the originally provided inventory start date. For update operations, the date supplied is interpreted as the inventory adjust date, is stored as such in the underlying data model, and is reflected in the QuickBooks Online UI." QtyOnHand is re-asserted the same way. Both are "Required for Inventory type items", so neither can simply be dropped from the body — refuse the update instead, matching create_item's existing Service/NonInventory restriction. Intuit also documents inactivation as "Not valid for Category item types", so an Active change on a Category is refused too. The Item update also posted to a bare endpoint. Intuit: "Add the query parameter, include=donotupdateaccountontxns, to the endpoint to supress updating the income or expense account on any existing transactions associated with this Item object." Without it, changing an item's account rewrote every historical transaction linked to it. The parameter is documented on the Item update alone, so it is not applied to any other entity. Also aligns the shared plumbing with the documented model: - PhysicalAddress documents Line1-Line5; the write map carried only Line1/Line2, so an address Sim had just read could not be written back. - Fault.type (ValidationFault / SystemFault / AuthenticationFault / AuthorizationFault) was discarded, hiding the classification that separates a bad payload from a dead token. Matched by prefix because Intuit's pages disagree between "ValidationFault" and type="Validation". - The query string used the form-encoded "+" for spaces; Intuit's own example percent-encodes them. - MAXRESULTS was capped at 100 against a documented maximum of 1,000. - Validates documented constraints locally: DisplayName <=500, Item.Name <=100 with no tabs, new lines, or colons, and an email address Intuit can store. - update_item's activeStatus and update_employee's displayName now carry the Category and Payroll caveats Intuit documents. Adds the missing query-builder coverage and a full-update test that would catch feeding the sanitized record into the merge, which would null every vendor's TaxIdentifier. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): gate every internal tool operation and contract-bind the provider ops The QuickBooks internal handler returned for all twelve provider create/update tool ids above both the operation input cap and the trusted-identity check, so those gates only ever ran for the three file tool ids. Hoist both above the switch, matching the Asana handler. The same twelve operations had no boundary schema — executeToolOperationImplementation only checks the input is a non-array object before casting to the operation's param type. Author a contract per operation and route them through executeInternalJsonToolOperation, the canonical in-process path. The two file operations now parse through their contracts as well, which were previously declared but unreferenced. Also pass the transfer signal, not the caller's, when reading a failed transaction-PDF response body, so a stalled Intuit error body stays bounded by the 60s transfer deadline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): stop persisting the Intuit identity token and share the webhook batch ceiling The OIDC identity JWT is only meaningful at connection time, where profile.accountId is already derived from it. Persisting it projected the token into the credential payload of every QuickBooks tool call, none of which read it. Gating it in token-resolution instead would break Shopify, which reads params.idToken as a shop domain fallback. Also exports QUICKBOOKS_WEBHOOK_MAX_EVENTS from the contract so the route no longer carries a second copy of the batch ceiling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): stop replace-allocations detaching non-invoice payment links buildPaymentLines returned only invoice LinkedTxn entries, and the unapplyOmittedInvoices path sent that array as the payment's entire Line collection. Intuit documents Payment Line.LinkedTxn.TxnType as one of Expense, Check, CreditCardCredit, JournalEntry, CreditMemo or Invoice, and an update as "send all the Lines that need to be present MINUS the lines that need to be removed" — so replacing invoice allocations silently detached every applied credit memo, expense, check and journal entry. Non-invoice lines are now carried forward first, in the order QuickBooks returned them, and counted against the payment total. Also aligned with Intuit's documented model: - salesreceiptrequest requires only Line, refundreceiptrequest only DepositToAccountRef and Line; neither lists CustomerRef, so customerId is optional on both receipt paths and CustomerRef is emitted only when given. - Line.Amount, SalesItemLineDetail.Qty and UnitPrice carry no positivity or non-zero constraint, so zero is accepted (finite/2-decimal/safe-range checks unchanged). - Enforce the documented maximum lengths locally: DocNumber 21, MemoRef.value 1000, Line.Description 4000. - Add void_sales_receipt, the documented salesreceipt?operation=update&include=void sparse void. - read_sales_transactions maxResults description now says 1–1000, matching validateQuickBooksPagination. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): align purchasing and accounting tools with Intuit's model Validated against Intuit's live machine-readable model files (EntityJsonObject_v1.json, CodesModelsJsonObjects_v2.json). - CurrencyRef is "Conditionally required" on all seven purchasing and accounting create models ("This must be defined if multicurrency is enabled for the company") and was never written, so every create failed on a multicurrency company. Creates now accept an ISO 4217 currencyCode. - GlobalTaxCalculation is "Conditionally required" on Bill, VendorCredit, PurchaseOrder, JournalEntry, and Deposit, and Optional on Purchase ("Not applicable to US companies; required for non-US companies"). Those six creates now accept it; JournalEntry accepts only the two values Intuit documents for it. BillPayment has no such property and is left alone. - Add void_bill_payment, the documented POST /billpayment?operation=update&include=void operation. - itembasedexpenselinedetail.Required is [] and ItemRef is Optional, so an item line no longer requires itemId. - The Deposit sparse update leaves "missing elements untouched", so depositAccountId is no longer forced on every update. - BillPayment DocNumber and APAccountRef are Optional writable fields and were unreachable; PurchaseOrder DueDate was emitted by the body builder but had no parameter. - Correct the maxResults range in the two read tools to 1-1000, matching validateQuickBooksPagination. - Record the unresolved BillPayment "Line [0..n]" / requiredFlag Required contradiction as a TSDoc note; behavior unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): correct the sparse-void TSDoc and narrow receipt customerId types The sparse guard's TSDoc claimed Intuit documents `sparse` as required to void any object. That is false for Invoice, whose void request model is `deleterequest` (Id + SyncToken, no sparse); only the `include=void` form carries it. The code was already correct; the comment would have led an editor to 'fix' void_invoice.ts into breaking it. Also narrows QuickBooksCreateSalesReceiptParams.customerId to optional so the type matches the required:false declaration, per salesreceiptrequest.Required listing only Line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): align reports and attachments with Intuit's report catalog Every capability flag is now transcribed from that report's own `*query` model in Intuit's machine-readable report catalog, which is the source the developer docs render their parameter tables from. Reports: - ap_aging_detail now advertises accountingMethod; `agedpayabledetailquery` documents `accounting_method`, so Sim was throwing on a request Intuit accepts. - ap_aging_summary now accepts customerId; `agedpayablesquery` documents `customer`. - Adds trial_balance_fr. Intuit documents one report with two endpoints — TrialBalanceFR for FR-locale companies, TrialBalance otherwise. - Adds the ten remaining documented report endpoints: AccountList, CustomerBalanceDetail, CustomerIncome, GeneralLedger, InventoryValuationDetail, InventoryValuationSummary, ClassSales, DepartmentSales, TaxSummary, VendorBalanceDetail. Each one's flags come from its own query model. - Exposes `date_macro` and `qzurl`, both documented per-report query params. `qzurl` is what populates the quick-zoom `href` links the row outputs already declare, and `date_macro` is mutually exclusive with an explicit date range. - Exposes the `employee` filter that only `profitandlossdetailquery` documents, and adds the `Employee` report-header echo Intuit's `reportheader` model lists. Attachments: - parseQuickBooksAttachableResponse takes an operation label. Reading an attachment by id reported failures as "attachment upload failed". - A dotless file name no longer reports itself as its own extension, so an unattachable `backup` is refused as extensionless rather than as "the backup file type". - Records why `.jpg` is canonicalized to image/jpeg: QuickBooks normalizes content type on ingest and `attachablerequest` has no ContentType property. Replaces the file_operations tool-wiring tests, which asserted only that `operation.input` is an identity projection, with coverage of the extension allowlist, MIME canonicalization, file-name sanitization, Attachable metadata, and fault labelling. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): document-align the bill payment account check and refund receipt update Intuit's BillPaymentCheck.BankAccountRef requires "Account.AccountType set to Bank and Account.AccountSubType set to Checking", and BillPaymentCreditCard.CCAccountRef requires "AccountType set to Credit Card and AccountSubType set to CreditCard". The create-bill-payment guard only compared AccountType, so a Savings or Line-of-Credit account passed the local check and failed at Intuit. Intuit documents RefundReceipt::UPDATE "Sparse update a refund receipt", which "only elements specified in the request are updated. Missing elements are left untouched." The operation used the read-merge-write full update instead, adding a round trip and a read/write race. Invoice, Estimate and SalesReceipt already post their sparse bodies directly; RefundReceipt now matches. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): describe the refund receipt update as sparse The update path now posts a sparse body directly instead of read-merge-write, per RefundReceipt::UPDATE 'Sparse update a refund receipt'. The LLM-facing tool description still claimed a full update. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): wire the block and registry to the Wave 1-2 tool changes Registers the two Wave 2 void tools, feeds the create parameters that had no UI, and corrects the block-level and report-metadata defects, all against Intuit's published request/response and report query models. Registration - Export and register quickbooks_void_sales_receipt and quickbooks_void_bill_payment; both were reachable from no surface. - Mirror the void_customer_payment sites: operation option, canvas sentence, transaction ID / sync token / confirm conditions, tools.access, and params. Parameters that existed on tools but had no UI - currencyCode on every create whose request model marks CurrencyRef conditionally required under multicurrency. - globalTaxCalculation on the same set minus bill payment, which billpaymentresponse does not carry; JournalEntry narrows to the two values its model documents. - dueDate on create and update purchase order, apAccountId and documentNumber on create bill payment. - Report date macro, quick-zoom links, and the employee filter; the quick-zoom string is coerced in tools.config.params, never in tools.config.tool. - Report dropdown now offers all 26 documented reports. Block defects - Sales receipt and refund receipt no longer require a customer; Intuit's salesreceiptrequest and refundreceiptrequest do not list CustomerRef. - Pagination accepts the documented ceiling of 1000, not 100. - The attachment file name no longer means two opposite things: the upload override is scoped to Add Attachment in File mode like its siblings, and the saved-file name gets its own field. - Item account fields explain the locale rule instead of a bare placeholder, and the inconsistent advanced/basic pairs are aligned. Report metadata - summarize_column_by collapses to the single twelve-value list every one of the fourteen documenting models shares, Employees included. - appaid, arpaid, and group_by are gated per report: the customer and vendor balance models and inventoryvaluationdetailquery document them too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): split the dual-semantic transactionId and register the missing subblock migrations `transactionId` was one control across the three by-ID reads and all fourteen updates and voids, and `tools.config.params` republished that single stored value as the read target, `paymentId`, `billId`, `purchaseOrderId`, `journalEntryId` and the rest. Because subblock values are keyed by ID and are never cleared when the operation changes, a bill ID read under Read Purchasing Transactions survived a switch to Update Purchase Order and addressed the wrong entity while the block still validated. The read path moves to `readTransactionId` and `transactionId` keeps the mutations. Registers the operation-scoped migration for that rename plus the four fields an earlier change orphaned without one: the three `summarize_column_by` subsets that collapsed into `reportSummarizeBy`, and the download-side `attachmentFileName` that moved to `downloadAttachmentFileName`. `syncToken` is left alone: it is live only across updates and voids, never across the read/mutate boundary, and carries one value space. A stale token after an operation switch is rejected by QuickBooks rather than silently targeting the wrong entity. Regenerates the integration docs, tool metadata, and integration catalog, which were already stale on this branch from the Wave 1-3 tool description changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * fix(quickbooks): declare the bill payment and purchase order fields the contracts dropped A contract body is a Zod object, so any key it does not declare is stripped before the provider operation runs - silently, with no validation error. The contracts were authored before currencyCode/apAccountId/documentNumber were added to Create Bill Payment and dueDate to Update Purchase Order, so those params were dead: the block forwarded them and they never reached Intuit. Adds a parity test across all twelve contract-bound operations so a param added to a tool without its contract fails instead of silently disappearing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB * test(quickbooks): extend contract parity coverage to the file operations The download body is a discriminated union and the add-attachment body carries a superRefine, so neither exposes a flat shape - but their declared keys are still introspectable, so all three file tools are now held to the same parity rule as the twelve JSON operations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…7558) * fix(anthropic): keep enum and const as structured-output constraints The SDK's transformJSONSchema pops every keyword it recognises and stringifies whatever is left into the node's description. It has no branch for enum or const, so both were silently demoted from grammar constraints into prose, leaving native structured outputs unenforced for them on every Anthropic and Azure Anthropic agent block. Lift enum and const out before the transform and re-attach them after, so the transform's sanitising is preserved verbatim. Only lift the shapes the API grammar-checks: a non-empty enum of primitives matching the node's declared type, and a primitive const. Anything else stays in place and is demoted exactly as before, since sending it would make the API reject the whole request. * chore(anthropic): type the structured-output test helper instead of any Replaces the two Record<string, any> casts in the new test helper with a WireSchemaNode interface describing the subset of the wire schema the assertions read back, per the repo's no-any rule. * test(anthropic): lock the structured-output schema non-mutation invariant The builder deep-clones before walking so it never deletes enum/const from the caller's schema object. Agent blocks inside a parallel or loop reuse the same responseFormat object across iterations, so losing that clone would strip the constraints from every iteration after the first. Verified the test fails when the clone is removed.
…py fixes (#7560) Comparison pages get three optional profile fields — a direct-answer lead paragraph, an "Is Sim better than {name}?" verdict, and per-section lead-ins — and the single feature table splits into one H2 + table per section so each section is quotable on its own. Dust: adds the lead paragraph, the verdict section, and all seven section lead-ins. OpenAI AgentKit: adds section lead-ins (with contextual links to Sim's pricing, docs, and self-hosting pages), drops the repeated "shutdown November 30, 2026" clause from the eight rows that restated it (the date stays in the intro and the shutdown limitation card), shortens the custom-blocks, agent-skills, and environment-promotion rows, and states the built-in-vs-third-party-MCP distinction directly instead of announcing it.
…per token (#7559) * fix(copilot): stream append previews as deltas instead of a snapshot per token An `append` preview is `existingContent + streamed`, so forcing a full snapshot on every emission re-sent the entire file once per streamed token. Cost is `O(file x tokens)` into the stream buffer, whose only trim is a 100,000-member rank cap — a count cap does not bound a member whose size is the file. One 250 KB file cost gigabytes. The forced snapshot was correct when it was written: the preview consumer could not merge a delta. It gained that ability two months later and the producer was never relaxed, so the branch has been dead weight since — `update` has shipped deltas through the same reducer ever since, and `deriveFilePreviewSession` is the only thing in the app that reads `contentMode`. Removing it changes no rendered text. The two conditions that make a snapshot necessary still force one: a base that diverges mid-stream fails `startsWith`, and the checkpoint interval still emits a recoverable full snapshot so no delta chain runs longer than a second. `file-preview-append-roundtrip.test.ts` drives the real producer into the real consumer and asserts the reconstructed text is byte-identical, including at token-scale chunking over a 250 KB base, across a diverging base, and under duplicate delivery. Both halves were verified to fail when the behaviour they pin is reverted. Measured against the session that prompted this: 2.29 GiB -> 55 MB. * chore(rules): drop the Redis payload rule until it has enforcement behind it The transferable part of it — cap bytes, never entries — is real: three independent writers made that exact mistake. But a rule pointing at a budget module that two writers use and three ignore is prose, not a rule. It belongs in `sim-caching.md`, trimmed to the part that transfers, alongside the change that routes those writers through `redis-budget.server.ts`. * test(copilot): pin that a failed delivery still reaches the stream buffer Streaming preview content as deltas is only safe because the replay chain is complete: `publish` persists after enqueuing and unconditionally, and a failed enqueue marks the client disconnected rather than throwing. So an envelope the client never received is still in Redis, and the producer is never told a delivery failed — it cannot advance past a gap the buffer does not have. That invariant was load-bearing and untested. Making persistence conditional on delivery now fails this test.
Contributor
|
Too many files changed for review (141 files, 100 file limit). Bypass the limit by tagging |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
…hs (#7561) * test(comparisons): cover the split-table and prose-link rendering paths Follow-up to #7560, which shipped the seven-table split without automated coverage. Guards the regressions that split makes possible: a silently dropped fact group, a section heading whose id no longer pairs with its aria-labelledby, a table label that stops distinguishing the seven tables, and a prose link that loses its external hardening or stops routing an internal path through Next. Covers one profile with every optional prose field and one with none. Each assertion was verified red against a mutated build before landing. * test(comparisons): assert the prose bodies render, not just their headings The section-presence assertions checked the verdict heading and its id but never the rendered prose, so they passed against a build that emitted an empty lead answer, verdict, or section intro. Assert the text itself, derived from the profile data, and assert its absence on a profile that supplies none. Verified red against a build that keeps the headings and empties the bodies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.