Compare commits

..
Author SHA1 Message Date
Kit Langton 1b113c9647 fix(core): tighten ansi-quote and double-quoted parameter desync checks 2026-10-05 03:26:36 -07:00
Kit Langton d5debc3b78 fix(core): close shell scanner subscript, conditional, and quote desyncs 2026-10-05 02:58:31 -07:00
Kit Langton a1974140eb fix(core): harden shell scanner subscripts, escape decoders, and continuations 2026-10-05 01:41:27 -07:00
Kit Langton f6642098e7 fix(core): harden shell scanner parameter, subscript, ansi-quote, and zsh builtin checks 2026-10-05 00:11:23 -07:00
Kit Langton a6320dfc25 fix(core): close remaining shell scanner soundness gaps and false opacity 2026-10-04 22:20:48 -07:00
Kit Langton a61ed90513 test(core): verify oracle fixture execution only in strict mode 2026-10-04 21:16:43 -07:00
Kit Langton f8dd0ae352 test(core): drop empty known-gap scaffolding 2026-10-04 21:04:45 -07:00
Kit Langton e8cbdb8a7f fix(core): fail closed on zsh array adjacency and dash script aliases 2026-10-04 21:04:45 -07:00
Kit Langton d768766dc4 fix(core): fail closed on zsh flag groups and glob modifiers 2026-10-04 21:04:45 -07:00
Kit Langton fa69d1d6dc fix(core): check bash arithmetic contexts and bound values for literal expansions 2026-10-04 21:04:45 -07:00
Kit Langton 5bcebfa47d fix(core): reject quote-hidden parameter ends in bash arithmetic quotes 2026-10-04 21:04:45 -07:00
Kit Langton 005e195065 fix(core): fail closed on bash arithmetic that rereads as a subshell 2026-10-04 21:04:45 -07:00
Kit Langton 89b4f2cf9b fix(core): scan dash double parentheses under a posix dialect 2026-10-04 21:04:45 -07:00
Kit Langton 454d47497d fix(core): share pending bash heredocs across nested scans 2026-10-04 21:04:45 -07:00
Kit Langton 3993dce7c9 test(core): add failing fixtures for reviewer-confirmed scanner misses 2026-10-04 21:04:44 -07:00
Kit Langton e7ae5a7725 fix(core): limit bash subscript evaluation checks to shell sinks 2026-10-04 21:04:44 -07:00
Kit Langton 8a6f4a11ab docs(core): note the runtime limit of bash subscript checks 2026-10-04 21:04:44 -07:00
Kit Langton 81926b2e1b refactor(core): skip the bash unit scanner for ordinary characters 2026-10-04 21:04:44 -07:00
Kit Langton 66329a0e4f test(core): generate nested real-shell oracle fixtures from one list 2026-10-04 21:04:44 -07:00
Kit Langton 1cdc33d33b fix(core): mark every bash expansion in decoded subscript text 2026-10-04 21:04:44 -07:00
Kit Langton 2470f6027a refactor(core): read bash function heads and continuations declaratively 2026-10-04 21:04:44 -07:00
Kit Langton 527cfd6037 fix(core): fail closed on bash reserved words after redirects 2026-10-04 21:04:44 -07:00
Kit Langton bd7ab857a0 fix(core): scan posix for loops without an in list 2026-10-04 21:04:44 -07:00
Kit Langton 2cc542c9d0 fix(core): fail closed on zsh glob qualifier positions 2026-10-04 21:04:44 -07:00
Kit Langton f37512680a fix(core): fail closed on bash subscript and parameter evaluation 2026-10-04 21:04:44 -07:00
Kit Langton 6dd193c70d refactor(core): split bash and powershell directory word resolution 2026-10-04 21:04:44 -07:00
Kit Langton 46d263f2eb fix(core): bound bash heredoc continuation scanning 2026-10-04 21:04:44 -07:00
Kit Langton 1764faded7 fix(core): fail closed on words after a bash ampersand redirect 2026-10-04 21:04:43 -07:00
Kit Langton 7fc28a7361 fix(core): match bash operators and assignment subscripts structurally 2026-10-04 21:04:43 -07:00
Kit Langton 32f62edf9e fix(core): end case pattern words at bracket metacharacters 2026-10-04 21:04:43 -07:00
Kit Langton b9c934a93d fix(core): scan bash quoting and expansions with one primitive 2026-10-04 21:04:43 -07:00
Kit Langton 9926646c77 refactor(core): track bash list state in one place 2026-10-04 21:04:43 -07:00
Kit Langton 0974946864 refactor(core): remove dead bash scanner state and guards 2026-10-04 21:04:43 -07:00
Kit Langton d0e57b4697 test(core): add failing real-shell fixtures for scanner soundness gaps 2026-10-04 21:04:43 -07:00
Kit Langton f3bcd5372a test(core): skip real-shell oracle on Windows and format scanner 2026-10-04 21:04:43 -07:00
Kit Langton 0abbbe007f fix(core): tighten case and for header validation and expand oracle 2026-10-04 21:04:43 -07:00
Kit Langton 6d9efafbfe fix(core): unify bash command scanner with recursive-descent parsing
Replace bashDelimited and bashExpansion with recursive-descent parsing in
scanBash so nested subshells, command substitutions, process substitutions,
brace groups, and nofork substitutions use the same tokenizer and command
parser rather than a separate delimiter pre-scan.

Update redirect.test.ts for 'pwd && cd >out /outside', where a real shell
executes cd /outside after the redirect while legacy tree-sitter drops the
trailing directory operand.
2026-10-04 21:04:43 -07:00
Kit Langton f6d9a842b6 test(core): add real-shell soundness oracle for bash scanner 2026-10-04 21:04:42 -07:00
40 changed files with 2654 additions and 2564 deletions

No files matched your search

+27
View File
@@ -383,6 +383,7 @@
"mime-types": "3.0.2",
"tree-sitter-bash": "0.25.0",
"tree-sitter-powershell": "0.25.10",
"venice-ai-sdk-provider": "2.1.1",
"web-tree-sitter": "0.25.10",
"which": "6.0.1",
"zod": "catalog:",
@@ -3326,6 +3327,8 @@
"@webgpu/types": ["@webgpu/types@0.1.54", "", {}, "sha512-81oaalC8LFrXjhsczomEQ0u3jG+TqE6V9QHLA8GNZq/Rnot0KDugu3LhSYSlie8tSdooAN1Hov05asrUUp9qgg=="],
"@workflow/serde": ["@workflow/serde@4.1.0", "", {}, "sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ=="],
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.14", "", {}, "sha512-T4EDRUBVZYRldYApjEJiU0e1stYWaRAX7CuSnKzrpwdZKo53zGV8/pqfzV6FfwNl9YThD2OumQYvqtvjvgG7aQ=="],
"@yuuang/ffi-rs-android-arm64": ["@yuuang/ffi-rs-android-arm64@1.3.7", "", { "os": "android", "cpu": "arm64" }, "sha512-t6Wx3Xll6c07Nuk0k3xnZsxKFxlshm92i0U/BiTHc6kQbvu+fMJF+gKsj4yEj886jH51CM3EqZT9Xdhq9CdUVw=="],
@@ -3366,6 +3369,8 @@
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"ai": ["ai@7.0.66", "", { "dependencies": { "@ai-sdk/gateway": "4.0.52", "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wBUyoCYF3GVr+62nelBgR8YbpTSsMZrzFyOOjiwijylNSM2TFCW35C+Pml2vc59/WLMpyhS/LWZ55M+B9DAcSg=="],
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
@@ -5718,6 +5723,8 @@
"validate-npm-package-name": ["validate-npm-package-name@7.0.2", "", {}, "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A=="],
"venice-ai-sdk-provider": ["venice-ai-sdk-provider@2.1.1", "", { "dependencies": { "@ai-sdk/openai-compatible": "^2.0.51", "@ai-sdk/provider": "^3.0.10", "@ai-sdk/provider-utils": "^4.0.30" }, "peerDependencies": { "ai": "^6.0.90" } }, "sha512-w3OHkuzzKZ3r2TOxER6myBYzZJNoDqol+DUHu3NnfBN/GETnUVxecZJab0CHQQ8GZc0jjzpFymepjcLDPS4SQg=="],
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
@@ -6386,6 +6393,12 @@
"@vscode/emmet-helper/jsonc-parser": ["jsonc-parser@2.3.1", "", {}, "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg=="],
"ai/@ai-sdk/gateway": ["@ai-sdk/gateway@4.0.52", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-SXUM8jzzuTUJRq+EOgPd5to6DSx0EKslVn+IVZHbUEX6k/3vCPNrvjckbK26HnNxHU/STxm+zTSJteqrO+7Z0w=="],
"ai/@ai-sdk/provider": ["@ai-sdk/provider@4.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q=="],
"ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.27", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw=="],
"ansi-align/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
@@ -6690,6 +6703,12 @@
"unzipper/fs-extra": ["fs-extra@11.3.1", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g=="],
"venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@ai-sdk/provider-utils": "4.0.46" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-C99M0T0SpRkcCClmJxbQkpSqGmxLfh3NhTsNF3aNaUQZZ7oXN5sPWi9LGs49X5Q/r9FWxBYeZARXs15xxtGIig=="],
"venice-ai-sdk-provider/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="],
"venice-ai-sdk-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="],
"vite-plugin-dynamic-import/acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="],
"vite-plugin-icons-spritesheet/chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
@@ -7238,6 +7257,8 @@
"@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="],
"ai/@ai-sdk/provider-utils/undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="],
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"ansi-align/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
@@ -7536,6 +7557,10 @@
"tw-to-css/tailwindcss/postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.15", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-XeZW1CcDF2GMbH4wejW6xBRI2QCOgnkVYUnxoeDadB1mf85riL2bMUeDoh+6gJ/r4mjNfzUPW8OjLjvwTP0u1Q=="],
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.46", "", { "dependencies": { "@ai-sdk/provider": "3.0.15", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8", "undici": "^6.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-tEtld97plCFiYevsJuOkGkeuhQndeMWFBVrJS4AjnbD5AqrNSXRCe0p+BZ3Cju/sxDeeZ9ym3q9YUV8fASA7aQ=="],
"vitest/@vitest/expect/chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"vscode-languageserver/vscode-languageserver-protocol/vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
@@ -8414,6 +8439,8 @@
"tw-to-css/tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils/undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="],
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@astrojs/cloudflare/@cloudflare/vite-plugin/miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.2", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.1" }, "os": "darwin", "cpu": "arm64" }, "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg=="],
+3 -3
View File
@@ -1,7 +1,7 @@
{
"nodeModules": {
"x86_64-linux": "sha256-4AqU8dPEwo0ukoyEX1SuDVzsXQ7G/fIPnxoL400zH4M=",
"aarch64-linux": "sha256-p75sJYtR8a4oVKb1+Dgp8Kl+1onSmUw5NRv95bbV1+E=",
"aarch64-darwin": "sha256-Psyvbw/lGulysQlhK/MqcHVIwx2aCDTr+Pfr+gRWsG4="
"x86_64-linux": "sha256-2RlbJRTEKSliuUbAE2lAktX63JFR/RKAuLkcCou8wb4=",
"aarch64-linux": "sha256-P7DAE018lTJNGmttg87U9oaTAuw/wHlnMStdnXYKCr0=",
"aarch64-darwin": "sha256-N+NfV1ObOTnW+ez7As+CS+6ci/iRGCQ6cslvyQFCp6E="
}
}
-35
View File
@@ -106,41 +106,6 @@ for await (const event of ai.llm.stream(ai.llm.request(input))) {
await ai.dispose()
```
## Venice AI
`Venice` provides native Chat Completions with streaming tools and reasoning. `model` and `chat`
select the same API; credentials default to `VENICE_API_KEY`.
```ts
import { LLM } from "@opencode/ai"
import { Venice } from "@opencode/ai/providers"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const response = yield* LLM.generate({
model: Venice.configure({ apiKey: process.env.VENICE_API_KEY }).chat("qwen3-6-27b"),
prompt: "Explain this design.",
providerOptions: {
reasoningEffort: "high",
veniceParameters: { includeVeniceSystemPrompt: false },
},
})
console.log(response.text)
})
```
Effort lowers to `reasoning.effort`; `reasoning.enabled` and `reasoning.summary` are also available.
Supported effort levels and toggles depend on the selected model. Omitted controls preserve its defaults.
Venice's added system prompt is disabled by default, matching the previous OpenCode Venice SDK behavior.
Replay complete `response.message` values to retain signed/encrypted reasoning and Gemini thought
signatures, including per-tool signatures. Venice's encrypted scalar trailers are excluded from visible
reasoning but retained in provider metadata for replay. Cache affinity uses `promptCacheKey`, and cache-write
usage reads Venice's `cache_creation_input_tokens` field.
The native package entrypoint is `@opencode/ai/providers/venice`. Image generation, embeddings, Responses, and
client-side E2EE are not implemented by this provider.
## Experimental evaluation
Evaluation models compare shared state with typed choice, score, and boolean questions. The API is
+43 -35
View File
@@ -584,7 +584,10 @@ const serverToolResultType = (name: string): AnthropicServerToolResultType | und
return undefined
}
const lowerServerToolResult = Effect.fnUntraced(function* (part: ToolResultPart, providerMetadataKey: string) {
const lowerServerToolResult = Effect.fnUntraced(function* (
part: ToolResultPart,
providerMetadataKey: string,
) {
const wireType = serverToolResultType(part.name)
if (!wireType)
return yield* invalid(`Anthropic Messages does not know how to round-trip server tool result for ${part.name}`)
@@ -654,7 +657,10 @@ const citationsFromMetadata = (metadata: MediaPart["metadata"]): AnthropicDocume
const isHttpUrl = (value: string) => /^https?:\/\//i.test(value.trim())
const lowerMedia = Effect.fnUntraced(function* (part: MediaPart, breakpoints?: Cache.Breakpoints) {
const lowerMedia = Effect.fnUntraced(function* (
part: MediaPart,
breakpoints?: Cache.Breakpoints,
) {
const mime = part.media.mediaType.toLowerCase()
const cacheControlValue = breakpoints ? cacheControl(breakpoints, part.cache) : undefined
const fileId = fileIdFromMetadata(part.metadata)
@@ -798,6 +804,9 @@ const requireThinkingSignature = (request: LLMRequest) => {
return true
}
// Mid-conversation system messages became available with Opus 4.8 and version
// 5 of the other supported Claude families. Treat later family versions as
// compatible without assuming that every Anthropic Messages model is Claude.
// Opus 4.8 and every Claude 5 model accept mid-conversation system messages; later versions inherit support.
const supportsNativeSystemUpdates = (request: LLMRequest) => {
const version = claudeVersion(String(request.model.id))
@@ -806,9 +815,25 @@ const supportsNativeSystemUpdates = (request: LLMRequest) => {
return version.major >= 5
}
// Native system messages must follow a user turn (tool results count) or a paused server-tool turn.
const acceptsNativeSystemAfter = (message: AnthropicMessage | undefined) =>
message?.role === "user" || (message?.role === "assistant" && message.content.at(-1)?.type === "server_tool_use")
const endsInServerToolUse = (message: LLMRequest["messages"][number]) => {
const last = message.content.at(-1)
return message.role === "assistant" && last?.type === "tool-call" && last.providerExecuted === true
}
const canUseNativeSystemUpdate = (request: LLMRequest, index: number) => {
const previous = request.messages[index - 1]
const next = request.messages[index + 1]
// Vertex currently rejects/404s for a system message after local tool results,
// so fold it into the user tool-result turn across continuations and history.
if (request.model.route.id === "google-vertex-messages" && previous?.role === "tool") return false
return (
previous !== undefined &&
previous.role !== "system" &&
(previous.role === "user" || previous.role === "tool" || endsInServerToolUse(previous)) &&
next?.role !== "system" &&
(next === undefined || next.role === "assistant")
)
}
const splitsLocalToolResults = (messages: LLMRequest["messages"], index: number) => {
const pending = new Set<string>()
@@ -837,34 +862,12 @@ const lowerNativeSystemUpdate = Effect.fnUntraced(function* (
}
})
const lowerWrappedSystemUpdate = Effect.fnUntraced(function* (
message: LLMRequest["messages"][number],
const lowerMessages = Effect.fnUntraced(function* (
request: LLMRequest,
breakpoints: Cache.Breakpoints,
) {
const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message)
return { type: "text" as const, text: part.text, cache_control: cacheControl(breakpoints, part.cache) }
})
const appendToUserTurn = (messages: AnthropicMessage[], block: AnthropicUserBlock) => {
const last = messages.at(-1)
if (last?.role === "user") messages[messages.length - 1] = { role: "user", content: [...last.content, block] }
else messages.push({ role: "user", content: [block] })
}
const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, breakpoints: Cache.Breakpoints) {
const messages: AnthropicMessage[] = []
const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider)
// Text updates stay where they are unless a user turn follows them; then they move after the latest
// user turn, the nearest spot where Anthropic accepts a native system message.
const holdUpdates = supportsNativeSystemUpdates(request)
const held: Array<LLMRequest["messages"][number]> = []
const releaseHeld = Effect.fnUntraced(function* () {
const native = acceptsNativeSystemAfter(messages.findLast((message) => message.role !== "system"))
for (const update of held.splice(0)) {
if (native) messages.push(yield* lowerNativeSystemUpdate(update, breakpoints))
else appendToUserTurn(messages, yield* lowerWrappedSystemUpdate(update, breakpoints))
}
})
for (const [index, message] of request.messages.entries()) {
if (message.role === "system") {
@@ -876,8 +879,16 @@ const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, breakpoi
}
if (splitsLocalToolResults(request.messages, index))
return yield* invalid("Anthropic Messages system updates cannot split a local tool call from its tool result")
if (holdUpdates) held.push(message)
else appendToUserTurn(messages, yield* lowerWrappedSystemUpdate(message, breakpoints))
if (supportsNativeSystemUpdates(request) && canUseNativeSystemUpdate(request, index)) {
messages.push(yield* lowerNativeSystemUpdate(message, breakpoints))
continue
}
const part = yield* ProviderShared.wrappedSystemUpdate("Anthropic Messages", message)
const block = { type: "text" as const, text: part.text, cache_control: cacheControl(breakpoints, part.cache) }
const previous = messages.at(-1)
if (previous?.role === "user")
messages[messages.length - 1] = { role: "user", content: [...previous.content, block] }
else messages.push({ role: "user", content: [block] })
continue
}
@@ -953,9 +964,7 @@ const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, breakpoi
`Anthropic Messages assistant messages only support text, reasoning, and tool-call content for now`,
)
}
if (content.length === 0) continue
yield* releaseHeld()
messages.push({ role: "assistant", content })
if (content.length > 0) messages.push({ role: "assistant", content })
continue
}
@@ -976,7 +985,6 @@ const lowerMessages = Effect.fnUntraced(function* (request: LLMRequest, breakpoi
messages[messages.length - 1] = { role: "user", content: [...previous.content, ...content] }
else messages.push({ role: "user", content })
}
yield* releaseHeld()
return messages
})
+9 -13
View File
@@ -65,7 +65,7 @@ const ExtraContent = Schema.Struct({
})
const decodeExtraContent = (value: unknown) => Option.getOrUndefined(Schema.decodeUnknownOption(ExtraContent)(value))
export const OpenAIChatAssistantToolCall = Schema.Struct({
const OpenAIChatAssistantToolCall = Schema.Struct({
id: Schema.String,
type: Schema.tag("function"),
function: Schema.Struct({
@@ -134,7 +134,7 @@ const OpenAIChatUserContent = Schema.Union([
])
type OpenAIChatUserContent = Schema.Schema.Type<typeof OpenAIChatUserContent>
export const OpenAIChatMessage = Schema.Union([
const OpenAIChatMessage = Schema.Union([
Schema.Struct({
role: Schema.Literal("system"),
content: Schema.Union([Schema.String, Schema.Array(OpenAIChatUserContent)]),
@@ -200,7 +200,7 @@ export type OpenAIChatBody = Schema.Schema.Type<typeof OpenAIChatBody>
// The event schema is one decoded SSE `data:` payload. `Framing.sse` splits the
// byte stream into strings, then `Protocol.jsonEvent` decodes each string into
// this provider-native event shape.
export const OpenAIChatUsage = Schema.StructWithRest(
const OpenAIChatUsage = Schema.StructWithRest(
Schema.Struct({
prompt_tokens: optionalNull(Schema.Number),
completion_tokens: optionalNull(Schema.Number),
@@ -238,7 +238,7 @@ const OpenAIChatToolCallDeltaFunction = Schema.Struct({
arguments: optionalNull(Schema.String),
})
export const OpenAIChatToolCallDelta = Schema.Struct({
const OpenAIChatToolCallDelta = Schema.Struct({
index: optionalNull(Schema.Number),
id: optionalNull(Schema.String),
function: optionalNull(OpenAIChatToolCallDeltaFunction),
@@ -246,7 +246,7 @@ export const OpenAIChatToolCallDelta = Schema.Struct({
})
type OpenAIChatToolCallDelta = Schema.Schema.Type<typeof OpenAIChatToolCallDelta>
export const OpenAIChatDelta = Schema.StructWithRest(
const OpenAIChatDelta = Schema.StructWithRest(
Schema.Struct({
content: optionalNull(Schema.String),
refusal: optionalNull(Schema.String),
@@ -259,7 +259,7 @@ export const OpenAIChatDelta = Schema.StructWithRest(
[Schema.Record(Schema.String, Schema.Unknown)],
)
export const OpenAIChatChoice = Schema.StructWithRest(
const OpenAIChatChoice = Schema.StructWithRest(
Schema.Struct({
delta: optionalNull(OpenAIChatDelta),
finish_reason: optionalNull(Schema.String),
@@ -326,8 +326,6 @@ export interface ParserState {
interface LoweringOptions {
readonly cacheControl?: (cache: CacheHint | undefined) => OpenAIChatCacheControl | undefined
readonly toolCallID?: (id: string) => string
/** Project provider-specific fields from the exact source, even when other messages are dropped during lowering. */
readonly assistant?: (source: LLMRequest["messages"][number], message: OpenAIChatMessage) => OpenAIChatMessage
}
const lowerTool = (tool: ToolDefinition, options: LoweringOptions, supportsStrictMode: boolean): OpenAIChatTool => ({
@@ -541,10 +539,8 @@ const lowerMessage = Effect.fnUntraced(function* (
options: LoweringOptions & { readonly providerMetadataKey: string },
) {
if (message.role === "user") return [yield* lowerUserMessage(message, options)]
if (message.role === "assistant") {
const lowered = yield* lowerAssistantMessage(message, reasoningField, requireReasoning, options)
return [options.assistant?.(message, lowered) ?? lowered]
}
if (message.role === "assistant")
return [yield* lowerAssistantMessage(message, reasoningField, requireReasoning, options)]
return (yield* lowerToolMessages(message, options)).messages
})
@@ -1218,7 +1214,7 @@ const step = (state: ParserState, event: OpenAIChatEvent) =>
] as const
})
export const finishEvents = Effect.fnUntraced(function* (state: ParserState) {
const finishEvents = Effect.fnUntraced(function* (state: ParserState) {
if (state.finishReason === undefined && state.requireFinishReason)
return yield* new AIError({
reason: new InvalidProviderOutputError({
-374
View File
@@ -1,374 +0,0 @@
import { Effect, Schema } from "effect"
import { Protocol } from "../route/protocol.js"
import { AIError, LLMEvent, type LanguageModelCompatibility, type LLMRequest } from "../schema/index.js"
import { classifyProviderFailure } from "../provider-error.js"
import { OpenAIChat } from "./openai-chat.js"
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
import { cacheControl } from "./utils/cache.js"
// ---------------------------------------------------------------------------
// Public options and request body
// ---------------------------------------------------------------------------
export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | (string & {})
const Parameters = Schema.Struct({
enableWebSearch: Schema.optional(Schema.String),
enableWebScraping: Schema.optional(Schema.Boolean),
enableWebCitations: Schema.optional(Schema.Boolean),
enableXSearch: Schema.optional(Schema.Boolean),
stripThinkingResponse: Schema.optional(Schema.Boolean),
disableThinking: Schema.optional(Schema.Boolean),
includeVeniceSystemPrompt: Schema.optional(Schema.Boolean),
characterSlug: Schema.optional(Schema.String),
includeSearchResultsInStream: Schema.optional(Schema.Boolean),
returnSearchResultsAsDocuments: Schema.optional(Schema.Boolean),
})
const Reasoning = Schema.Struct({
effort: Schema.optional(Schema.String),
enabled: Schema.optional(Schema.Boolean),
summary: Schema.optional(Schema.String),
})
export type OptionsInput = {
readonly reasoningEffort?: ReasoningEffort
readonly reasoning?: {
readonly effort?: ReasoningEffort
readonly enabled?: boolean
readonly summary?: "auto" | "concise" | "detailed" | (string & {})
}
readonly veniceParameters?: Omit<typeof Parameters.Type, "enableWebSearch"> & {
readonly enableWebSearch?: "off" | "on" | "auto" | (string & {})
}
readonly promptCacheKey?: string
readonly promptCacheRetention?: "default" | "extended" | "24h" | (string & {})
readonly parallelToolCalls?: boolean
readonly maxCompletionTokens?: number
readonly maxTokens?: number
readonly minP?: number
readonly repetitionPenalty?: number
readonly stopTokenIds?: readonly number[]
readonly logprobs?: boolean
readonly topLogprobs?: number
readonly maxTemp?: number
readonly minTemp?: number
readonly responseFormat?: Readonly<Record<string, unknown>>
readonly user?: string
}
const Options = Schema.Struct({
reasoningEffort: Schema.optional(Schema.String),
reasoning: Schema.optional(Reasoning),
veniceParameters: Schema.optional(Parameters),
promptCacheKey: Schema.optional(Schema.String),
promptCacheRetention: Schema.optional(Schema.String),
parallelToolCalls: Schema.optional(Schema.Boolean),
maxCompletionTokens: Schema.optional(Schema.Number),
maxTokens: Schema.optional(Schema.Number),
minP: Schema.optional(Schema.Number),
repetitionPenalty: Schema.optional(Schema.Number),
stopTokenIds: Schema.optional(Schema.Array(Schema.Number)),
logprobs: Schema.optional(Schema.Boolean),
topLogprobs: Schema.optional(Schema.Number),
maxTemp: Schema.optional(Schema.Number),
minTemp: Schema.optional(Schema.Number),
responseFormat: Schema.optional(JsonObject),
user: Schema.optional(Schema.String),
})
const ToolCall = Schema.Struct({
...OpenAIChat.OpenAIChatAssistantToolCall.fields,
thought_signature: Schema.optional(Schema.String),
})
const Assistant = Schema.StructWithRest(
Schema.Struct({
...OpenAIChat.OpenAIChatMessage.cases.assistant.schema.fields,
tool_calls: optionalArray(ToolCall),
thought_signature: Schema.optional(Schema.String),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
)
const Messages = Schema.Array(
Schema.Union([
OpenAIChat.OpenAIChatMessage.cases.system,
OpenAIChat.OpenAIChatMessage.cases.user,
Assistant,
OpenAIChat.OpenAIChatMessage.cases.tool,
]),
)
const Body = Schema.Struct({
...OpenAIChat.bodyFields,
messages: Messages,
reasoning: Schema.optional(Reasoning),
venice_parameters: Schema.Record(Schema.String, Schema.Union([Schema.String, Schema.Boolean])),
prompt_cache_retention: Options.fields.promptCacheRetention,
parallel_tool_calls: Options.fields.parallelToolCalls,
min_p: Options.fields.minP,
top_k: Schema.optional(Schema.Number),
repetition_penalty: Options.fields.repetitionPenalty,
stop_token_ids: Options.fields.stopTokenIds,
logprobs: Options.fields.logprobs,
top_logprobs: Options.fields.topLogprobs,
max_temp: Options.fields.maxTemp,
min_temp: Options.fields.minTemp,
response_format: Options.fields.responseFormat,
user: Options.fields.user,
})
// ---------------------------------------------------------------------------
// Streaming schemas and state
// ---------------------------------------------------------------------------
const ToolDelta = Schema.Struct({
...OpenAIChat.OpenAIChatToolCallDelta.fields,
thought_signature: optionalNull(Schema.String),
})
const Delta = Schema.StructWithRest(
Schema.Struct({
...OpenAIChat.OpenAIChatDelta.schema.fields,
tool_calls: optionalNull(Schema.Array(ToolDelta)),
thought_signature: optionalNull(Schema.String),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
)
const Choice = Schema.StructWithRest(
Schema.Struct({ ...OpenAIChat.OpenAIChatChoice.schema.fields, delta: optionalNull(Delta) }),
[Schema.Record(Schema.String, Schema.Unknown)],
)
const Usage = Schema.StructWithRest(
Schema.Struct({
...OpenAIChat.OpenAIChatUsage.schema.fields,
prompt_tokens_details: optionalNull(
Schema.StructWithRest(
Schema.Struct({
cached_tokens: optionalNull(Schema.Number),
cache_write_tokens: optionalNull(Schema.Number),
cache_creation_input_tokens: optionalNull(Schema.Number),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
),
),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
)
const Event = Schema.StructWithRest(
Schema.Struct({
...OpenAIChat.OpenAIChatEvent.schema.fields,
choices: optionalNull(Schema.Array(Choice)),
usage: optionalNull(Usage),
error: optionalNull(Schema.Union([Schema.String, OpenAIChat.OpenAIChatEvent.schema.fields.error.schema])),
issues: optionalArray(
Schema.Struct({
message: Schema.String,
path: Schema.optional(Schema.Array(Schema.Union([Schema.String, Schema.Number]))),
}),
),
}),
[Schema.Record(Schema.String, Schema.Unknown)],
)
interface State {
readonly shared: OpenAIChat.ParserState
readonly pending: string
readonly reasoning: string
readonly encrypted: boolean
readonly signature?: string
readonly tools: Readonly<Record<string, string>>
}
const MARKER = "__ENCRYPTED_REASONING__"
// ---------------------------------------------------------------------------
// Request lowering
// ---------------------------------------------------------------------------
const fromRequest = Effect.fn("VeniceChat.fromRequest")(function* (request: LLMRequest) {
const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(request.providerOptions ?? {})
const body = yield* OpenAIChat.fromRequest(request, {
cacheControl: cacheControl(),
assistant: (source, message) => {
if (message.role !== "assistant") return message
const parts = source.content.filter(
(part) => part.type === "text" || part.type === "reasoning" || part.type === "tool-call",
)
const calls = source.content.filter((part) => part.type === "tool-call")
const signature = parts
.map((part) => part.providerMetadata?.venice?.messageThoughtSignature)
.find((value) => typeof value === "string")
const raw = parts
.map((part) => part.providerMetadata?.venice?.encryptedReasoningContent)
.find((value) => typeof value === "string")
return {
...message,
...(signature === undefined ? {} : { thought_signature: signature }),
...(raw === undefined ? {} : { reasoning_content: raw }),
tool_calls: message.tool_calls?.map((call, index) => {
const signature = calls[index]?.providerMetadata?.venice?.thoughtSignature
return { ...call, ...(typeof signature === "string" ? { thought_signature: signature } : {}) }
}),
}
},
})
return {
...body,
max_completion_tokens: options.maxCompletionTokens ?? options.maxTokens ?? request.generation?.maxTokens,
reasoning_effort: undefined,
reasoning:
options.reasoningEffort === undefined
? options.reasoning
: { ...options.reasoning, effort: options.reasoningEffort },
// Match the previous Venice SDK default, not the gateway's added prompt.
venice_parameters: Object.fromEntries(
Object.entries({
...options.veniceParameters,
includeVeniceSystemPrompt: options.veniceParameters?.includeVeniceSystemPrompt ?? false,
})
.filter(([, value]) => value !== undefined)
.map(([key, value]) => [key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`), value]),
),
prompt_cache_retention: options.promptCacheRetention,
parallel_tool_calls: options.parallelToolCalls,
min_p: options.minP,
top_k: request.generation?.topK,
repetition_penalty: options.repetitionPenalty,
stop_token_ids: options.stopTokenIds,
logprobs: options.logprobs,
top_logprobs: options.topLogprobs,
max_temp: options.maxTemp,
min_temp: options.minTemp,
response_format: options.responseFormat,
user: options.user,
}
})
// ---------------------------------------------------------------------------
// Venice normalization around the shared Chat state machine
// ---------------------------------------------------------------------------
const step = Effect.fn("VeniceChat.step")(function* (state: State, event: typeof Event.Type) {
if (typeof event.error === "string")
return yield* new AIError({
reason: classifyProviderFailure({
message: [
event.error,
...(event.issues ?? []).map(
(issue) => `${issue.path?.length ? `${issue.path.join(".")}: ` : ""}${issue.message}`,
),
].join("; "),
rawBody: ProviderShared.encodeJson(event),
}),
})
const delta = event.choices?.[0]?.delta
const scalar = delta?.reasoning_content ?? ""
const text = state.pending + scalar
const marker = text.indexOf(MARKER)
const pending =
marker >= 0 || state.encrypted
? 0
: (Array.from({ length: Math.min(text.length, MARKER.length - 1) }, (_, index) => index + 1)
.filter((length) => text.endsWith(MARKER.slice(0, length)))
.at(-1) ?? 0)
const visible = state.encrypted ? "" : marker >= 0 ? text.slice(0, marker) : text.slice(0, text.length - pending)
const result = yield* OpenAIChat.protocol.stream.step(state.shared, {
...event,
error: event.error,
usage: event.usage
? {
...event.usage,
prompt_tokens_details: event.usage.prompt_tokens_details
? {
...event.usage.prompt_tokens_details,
cache_write_tokens:
event.usage.prompt_tokens_details.cache_write_tokens ??
event.usage.prompt_tokens_details.cache_creation_input_tokens,
}
: event.usage.prompt_tokens_details,
}
: event.usage,
choices: event.choices?.map((choice, index) =>
index === 0 && choice.delta
? {
...choice,
delta: {
...choice.delta,
reasoning_content: visible || undefined,
// Opaque-only scalar reasoning still needs a canonical part for replay.
reasoning_details: choice.delta.reasoning_details ?? (marker >= 0 ? [] : undefined),
},
}
: choice,
),
})
const tools = { ...state.tools }
for (const call of delta?.tool_calls ?? []) {
if (!call.thought_signature) continue
const index = call.index ?? result[0].latestToolIndex
const id =
call.id ?? (index === undefined ? undefined : (result[0].tools[index]?.id ?? result[0].pendingTools[index]?.id))
if (id) tools[id] = call.thought_signature
}
return [
{
shared: result[0],
pending: pending ? text.slice(-pending) : "",
reasoning: state.reasoning + scalar,
encrypted: state.encrypted || marker >= 0,
signature: delta?.thought_signature ?? state.signature,
tools,
},
result[1],
] as const
})
const onHalt = Effect.fn("VeniceChat.onHalt")(function* (state: State) {
const events = yield* OpenAIChat.finishEvents(state.shared)
return events.flatMap((event): LLMEvent[] => {
if (
event.type !== "reasoning-end" &&
event.type !== "text-end" &&
event.type !== "tool-call" &&
event.type !== "tool-input-end"
)
return [event]
const signature = event.type === "tool-call" || event.type === "tool-input-end" ? state.tools[event.id] : undefined
const providerMetadata = {
...event.providerMetadata,
venice: {
...event.providerMetadata?.venice,
...(state.signature ? { messageThoughtSignature: state.signature } : {}),
...(signature ? { thoughtSignature: signature } : {}),
...(event.type === "reasoning-end" && state.encrypted ? { encryptedReasoningContent: state.reasoning } : {}),
},
}
if (event.type === "reasoning-end" && state.pending)
return [LLMEvent.reasoningDelta({ id: event.id, text: state.pending }), { ...event, providerMetadata }]
return [{ ...event, providerMetadata }]
})
})
export const compatibility = {
maxTokensField: "max_completion_tokens",
supportsStore: false,
supportsPromptCacheKey: true,
} satisfies LanguageModelCompatibility
export const protocol = Protocol.make({
id: "venice-chat",
body: { schema: Body, from: fromRequest },
stream: {
event: Schema.Union([Schema.Literal("[DONE]"), Protocol.jsonEvent(Event)]),
initial: (request): State => ({
shared: OpenAIChat.protocol.stream.initial(request),
pending: "",
reasoning: "",
encrypted: false,
tools: {},
}),
step: (state: State, event) => (event === "[DONE]" ? Effect.succeed([state, []] as const) : step(state, event)),
terminal: (event) => event === "[DONE]",
onHalt,
},
})
export * as VeniceChat from "./venice-chat.js"
-1
View File
@@ -40,7 +40,6 @@ export * as Stability from "./stability.js"
export * as TogetherAI from "./togetherai.js"
export * as TypeSafeAI from "./typesafe-ai.js"
export * as VercelAIGateway from "./vercel-ai-gateway.js"
export * as Venice from "./venice.js"
export * as XAI from "./xai.js"
export * as ZAI from "./zai.js"
export * as ZAICodingPlan from "./zai-coding-plan.js"
-62
View File
@@ -1,62 +0,0 @@
import type { ProviderPackage } from "../provider-package.js"
import { VeniceChat } from "../protocols/venice-chat.js"
import { OpenAIChat } from "../protocols/openai-chat.js"
import { AuthOptions, type ProviderAuthOption } from "../route/auth-options.js"
import { Route, type RouteDefaultsInput } from "../route/client.js"
import { Endpoint } from "../route/endpoint.js"
import { ProviderID, type ModelID } from "../schema/index.js"
export const id = ProviderID.make("venice")
export type ChatOptionsInput = VeniceChat.OptionsInput
export type Config = Omit<RouteDefaultsInput, "providerOptions"> &
ProviderAuthOption<"optional"> & {
readonly baseURL?: string
readonly queryParams?: Readonly<Record<string, string>>
readonly providerOptions?: ChatOptionsInput
}
export type Settings = ProviderPackage.Settings &
ChatOptionsInput & {
readonly apiKey?: string
readonly baseURL?: string
readonly queryParams?: Readonly<Record<string, string>>
}
const route = Route.make({
id: "venice-chat",
provider: id,
providerMetadataKey: "venice",
protocol: VeniceChat.protocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: "https://api.venice.ai/api/v1" }),
framing: OpenAIChat.framing,
})
export const routes = [route]
export const configure = (input: Config = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL, queryParams, ...rest } = input
const chat = (modelID: string | ModelID) =>
route
.with({
...rest,
endpoint: { baseURL: baseURL ?? route.endpoint.baseURL, query: queryParams },
auth: AuthOptions.bearer(input, "VENICE_API_KEY"),
})
.model<ChatOptionsInput>({ id: modelID, compatibility: VeniceChat.compatibility })
return { id, model: chat, chat, configure }
}
export const provider = configure()
export const chat = provider.chat
export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["model"] = (
modelID,
{ apiKey, baseURL, queryParams, body, headers, ...providerOptions },
) =>
configure({
apiKey,
baseURL,
queryParams,
headers,
http: body === undefined ? undefined : { body: { ...body } },
providerOptions,
}).model(modelID)
export * as Venice from "./venice.js"
-27
View File
@@ -171,33 +171,6 @@ describe("Anthropic Messages effort updates", () => {
}),
)
it.effect("releases a held system update next to an effort marker as one valid section", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model: opus5,
messages: [
Message.user("Fix it."),
Message.assistant("Done."),
lowFromHigh,
Message.system("Update."),
Message.user("Next."),
],
providerOptions: { effort: "low" },
cache: "none",
}),
)
expect(prepared.body.messages).toEqual([
{ role: "user", content: [{ type: "text", text: "Fix it." }] },
{ role: "assistant", content: [{ type: "text", text: "Done." }] },
{ role: "system", content: [], output_config: { effort: "low" } },
{ role: "user", content: [{ type: "text", text: "Next." }] },
{ role: "system", content: [{ type: "text", text: "Update.", cache_control: undefined }] },
])
}),
)
it.effect("falls back to a plain top-level effort when history drifted from the current effort", () =>
Effect.gen(function* () {
const drifted = yield* compileRequest(
@@ -1,75 +0,0 @@
{
"version": 1,
"metadata": {
"model": "claude-opus-4-8",
"tags": [
"prefix:venice-chat",
"provider:venice",
"protocol:venice-chat",
"reasoning",
"tool",
"tool-loop",
"continuation",
"usage",
"signed"
],
"name": "venice-chat/claude-opus-4-8-preserves-reasoning-through-a-tool-loop-and-follow-up",
"recordedAt": "2026-10-05T03:00:37.127Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"claude-opus-4-8\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169233,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\"},\"finish_reason\":null}]}\n\n: processing\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"I need to find the rate for\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"I need to find the rate for\"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" ZEBRA.\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\" ZEBRA.\"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"\\n\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"\\n\\n\"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"\",\"signature\":\"EsECCrwBCBIYAipASM082xA/ckQU1q1MLSuAGRD/F94kvqoBlyXoEKdxvH2f2kn+ESR0AZfmYtv8VB8ux7LQ+qq4DnODYQ2iBHemajIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiQ1YzJjNzI5YS0wMzVjLTQ5ZDYtODNiOC03NGNhZTAyMzU2NWRyEOtICOPsjcfgmcoVEjPetDWIAQGaAREKD2NsYXVkZS1vcHVzLTQtOKgB0qWM1gawAQISDFJE1AThUPJ/0OxIBxoMak46jCiQRsOzj1nIIjBV1UQ4WoUBtZX8F6kyFtKyRPkLdWp7WQKQYd+f4VT3GvmEqenUY2ANNJImtDj/BQAqMhz7JVFNqrlemYzJNRbF6ntHDPc2enCrd/tYXBG6nms1qbcfKWc7CYc2ZGogOqiItKNDGAE=\",\"id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\"}],\"tool_calls\":[{\"index\":0,\"id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"\"}}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"code\\\"\"}}]},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\": \\\"ZEBR\"}}]},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"A\\\"}\"}}]},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}]}\n\ndata: {\"id\":\"msg_011CfiLtM3Rj4dVUWFJAEjoy\",\"object\":\"chat.completion.chunk\",\"created\":1791169234,\"model\":\"claude-opus-4-8\",\"choices\":[],\"usage\":{\"prompt_tokens\":499,\"completion_tokens\":74,\"total_tokens\":573,\"prompt_tokens_details\":{\"cached_tokens\":0}},\"cost\":{\"usd\":0.005214,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"claude-opus-4-8\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"I need to find the rate for ZEBRA.\\n\\n\",\"id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"signature\":\"EsECCrwBCBIYAipASM082xA/ckQU1q1MLSuAGRD/F94kvqoBlyXoEKdxvH2f2kn+ESR0AZfmYtv8VB8ux7LQ+qq4DnODYQ2iBHemajIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiQ1YzJjNzI5YS0wMzVjLTQ5ZDYtODNiOC03NGNhZTAyMzU2NWRyEOtICOPsjcfgmcoVEjPetDWIAQGaAREKD2NsYXVkZS1vcHVzLTQtOKgB0qWM1gawAQISDFJE1AThUPJ/0OxIBxoMak46jCiQRsOzj1nIIjBV1UQ4WoUBtZX8F6kyFtKyRPkLdWp7WQKQYd+f4VT3GvmEqenUY2ANNJImtDj/BQAqMhz7JVFNqrlemYzJNRbF6ntHDPc2enCrd/tYXBG6nms1qbcfKWc7CYc2ZGogOqiItKNDGAE=\"}],\"reasoning_content\":\"I need to find the rate for ZEBRA.\\n\\n\"},{\"role\":\"tool\",\"tool_call_id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169235,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\"},\"finish_reason\":null}]}\n\n: processing\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"23 times 17 is \",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"23 times 17 is \"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"391, plus 9 g\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"391, plus 9 g\"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"ives 400.\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"ives 400.\"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"\\n\\n\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"\\n\\n\"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"text\":\"\",\"signature\":\"ErgCCrwBCBIYAipAzm6Uz1fiUimJZ7ry16/5jOPhPfQ9Zm3zSb449atO4+Dv3KroxOwD17dLjPUhdRWeFNghT/lQsCRlsY9T6SWyMzIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiQ1YzJjNzI5YS0wMzVjLTQ5ZDYtODNiOC03NGNhZTAyMzU2NWRyEGhpfnmythMRLK5wcAMHasmIAQGaAREKD2NsYXVkZS1vcHVzLTQtOKgB1KWM1gawAQISDA4K+cR55K4LQVXyhhoMBUHsSBuaNQlHp5ZPIjBvSX58DhVBxFRl9sVZYgZwrMDqGzC1/tgyH1wflAP4TkHTPje+Gdbv0yUxBBIQtaEqKSS4Bh4olHSAKxLEG1SyH4Iy46lp50SxdXxGJ/PIwTfHI47SEaE9pEfNGAE=\"}],\"content\":\"\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"400\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: {\"id\":\"msg_011CfiLtUi3ffKFSXdhWtFbp\",\"object\":\"chat.completion.chunk\",\"created\":1791169236,\"model\":\"claude-opus-4-8\",\"choices\":[],\"usage\":{\"prompt_tokens\":183,\"completion_tokens\":23,\"total_tokens\":206,\"prompt_tokens_details\":{\"cached_tokens\":0}},\"cost\":{\"usd\":0.001788,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"claude-opus-4-8\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"I need to find the rate for ZEBRA.\\n\\n\",\"id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"signature\":\"EsECCrwBCBIYAipASM082xA/ckQU1q1MLSuAGRD/F94kvqoBlyXoEKdxvH2f2kn+ESR0AZfmYtv8VB8ux7LQ+qq4DnODYQ2iBHemajIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiQ1YzJjNzI5YS0wMzVjLTQ5ZDYtODNiOC03NGNhZTAyMzU2NWRyEOtICOPsjcfgmcoVEjPetDWIAQGaAREKD2NsYXVkZS1vcHVzLTQtOKgB0qWM1gawAQISDFJE1AThUPJ/0OxIBxoMak46jCiQRsOzj1nIIjBV1UQ4WoUBtZX8F6kyFtKyRPkLdWp7WQKQYd+f4VT3GvmEqenUY2ANNJImtDj/BQAqMhz7JVFNqrlemYzJNRbF6ntHDPc2enCrd/tYXBG6nms1qbcfKWc7CYc2ZGogOqiItKNDGAE=\"}],\"reasoning_content\":\"I need to find the rate for ZEBRA.\\n\\n\"},{\"role\":\"tool\",\"tool_call_id\":\"toolu_01KYDseHbTDZXSYg9bNwiwxU\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"},{\"role\":\"assistant\",\"content\":\"400\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"23 times 17 is 391, plus 9 gives 400.\\n\\n\",\"format\":\"anthropic-claude-v1\",\"index\":0,\"signature\":\"ErgCCrwBCBIYAipAzm6Uz1fiUimJZ7ry16/5jOPhPfQ9Zm3zSb449atO4+Dv3KroxOwD17dLjPUhdRWeFNghT/lQsCRlsY9T6SWyMzIPY2xhdWRlLW9wdXMtNC04OABCCHRoaW5raW5nWiQ1YzJjNzI5YS0wMzVjLTQ5ZDYtODNiOC03NGNhZTAyMzU2NWRyEGhpfnmythMRLK5wcAMHasmIAQGaAREKD2NsYXVkZS1vcHVzLTQtOKgB1KWM1gawAQISDA4K+cR55K4LQVXyhhoMBUHsSBuaNQlHp5ZPIjBvSX58DhVBxFRl9sVZYgZwrMDqGzC1/tgyH1wflAP4TkHTPje+Gdbv0yUxBBIQtaEqKSS4Bh4olHSAKxLEG1SyH4Iy46lp50SxdXxGJ/PIwTfHI47SEaE9pEfNGAE=\"}],\"reasoning_content\":\"23 times 17 is 391, plus 9 gives 400.\\n\\n\"},{\"role\":\"user\",\"content\":\"Add 7 to your previous final answer. Reply with just the number.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"msg_011CfiLtbruiyeuxJmswkPPt\",\"object\":\"chat.completion.chunk\",\"created\":1791169237,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\"},\"finish_reason\":null}]}\n\n: processing\n\ndata: {\"id\":\"msg_011CfiLtbruiyeuxJmswkPPt\",\"object\":\"chat.completion.chunk\",\"created\":1791169237,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"407\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"msg_011CfiLtbruiyeuxJmswkPPt\",\"object\":\"chat.completion.chunk\",\"created\":1791169237,\"model\":\"claude-opus-4-8\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: {\"id\":\"msg_011CfiLtbruiyeuxJmswkPPt\",\"object\":\"chat.completion.chunk\",\"created\":1791169237,\"model\":\"claude-opus-4-8\",\"choices\":[],\"usage\":{\"prompt_tokens\":229,\"completion_tokens\":3,\"total_tokens\":232,\"prompt_tokens_details\":{\"cached_tokens\":0}},\"cost\":{\"usd\":0.001464,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -1,75 +0,0 @@
{
"version": 1,
"metadata": {
"model": "deepseek-v4-flash",
"tags": [
"prefix:venice-chat",
"provider:venice",
"protocol:venice-chat",
"reasoning",
"tool",
"tool-loop",
"continuation",
"usage",
"scalar"
],
"name": "venice-chat/deepseek-v4-flash-preserves-reasoning-through-a-tool-loop-and-follow-up",
"recordedAt": "2026-10-05T02:59:53.799Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"deepseek-v4-flash\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"Th\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"e user wants me to look\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" up the conversi\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"on rate fo\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"r code ZEBRA\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\", then mult\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"iply by\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" 17 and add 9. Let m\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"e start by lo\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"oking up the rate.\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":[{\"index\":0,\"id\":\"chatcmpl-tool-ab656a8b683341f2\",\"function\":{\"arguments\":\"\",\"name\":\"lookup_rate\"},\"type\":\"function\"}]},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-Rhr7pZI7Hv6zIafL3B1KoyzY\",\"object\":\"chat.completion.chunk\",\"created\":1791169188,\Line truncated
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"deepseek-v4-flash\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"chatcmpl-tool-ab656a8b683341f2\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_content\":\"The user wants me to look up the conversion rate for code ZEBRA, then multiply by 17 and add 9. Let me start by looking up the rate.\"},{\"role\":\"tool\",\"tool_call_id\":\"chatcmpl-tool-ab656a8b683341f2\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"We have the\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" rate: 2\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"3. Mul\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"tiply by\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" 17: 2\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"3 * 17\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" = 391.\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" Then ad\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"d 9:\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" 391 + 9 = 400.\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"400\",\"reasoning_content\":\" So the answer is 400.\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":\"stop\"}]}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RtwpMZBraGwqJiKRKQFrmmIq\",\"object\":\"chat.completion.chunk\",\"created\":1791169191,\"model\":\"deepseek-v4-flash\",\"choices\":[],\"usage\":{\"prompt_tokens\":128,\"completion_tokens\":46,\"total_tokens\":174,\"prompt_tokens_details\":{\"cached_tokens\":0}}Line truncated
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"deepseek-v4-flash\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"chatcmpl-tool-ab656a8b683341f2\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_content\":\"The user wants me to look up the conversion rate for code ZEBRA, then multiply by 17 and add 9. Let me start by looking up the rate.\"},{\"role\":\"tool\",\"tool_call_id\":\"chatcmpl-tool-ab656a8b683341f2\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"},{\"role\":\"assistant\",\"content\":\"400\",\"reasoning_content\":\"We have the rate: 23. Multiply by 17: 23 * 17 = 391. Then add 9: 391 + 9 = 400. So the answer is 400.\"},{\"role\":\"user\",\"content\":\"Add 7 to your previous final answer. Reply with just the number.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"We need to add\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" 7 to t\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"h\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"e previous ans\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"wer,\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" which\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\" was 400. S\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":\"o 400+7=407. Reply wi\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"407\",\"reasoning_content\":\"th just the number.\",\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":\"stop\"}]}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-R0MMWxoRA5NidBHTttxrMoVe\",\"object\":\"chat.completion.chunk\",\"created\":1791169192,\"model\":\"deepseek-v4-flash\",\"choices\":[],\"usage\":{\"prompt_tokens\":148,\"completion_tokens\":33,\"total_tokens\":181,\"prompt_tokens_details\":{\"cached_tokens\":0}},\"cost\":{\"usd\":0.000029499,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -1,75 +0,0 @@
{
"version": 1,
"metadata": {
"model": "gemini-3-1-pro-preview",
"tags": [
"prefix:venice-chat",
"provider:venice",
"protocol:venice-chat",
"reasoning",
"tool",
"tool-loop",
"continuation",
"usage",
"details"
],
"name": "venice-chat/gemini-3-1-pro-preview-preserves-reasoning-through-a-tool-loop-and-follow-up",
"recordedAt": "2026-10-05T03:00:58.104Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gemini-3-1-pro-preview\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"gen-1791169245-MjiWKnEhpgHVKzFcOlS0\",\"object\":\"chat.completion.chunk\",\"created\":1791169245,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"**Calculating ZEBRA Conversion**\\n\\nI'm currently processing the calculation involving ZEBRA's conversion rate. My next step is to use the `lookup_rate` tool with the code \\\"ZEBRA\\\" to fetch the necessary value, which will then be used in the full equation.\\n\\n\",\"format\":\"google-gemini-v1\",\"index\":0}],\"reasoning_content\":\"**Calculating ZEBRA Conversion**\\n\\nI'm currently processing the calculation involving ZEBRA's conversion rate. My next step is to use the `lookup_rate` tool with the code \\\"ZEBRA\\\" to fetch the necessary value, which will then be used in the full equation.\\n\\n\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169245-MjiWKnEhpgHVKzFcOlS0\",\"object\":\"chat.completion.chunk\",\"created\":1791169245,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_details\":[{\"type\":\"reasoning.encrypted\",\"data\":\"AY89a19Tkd2zuyuB3m6as2cMeOoFk8xFfcBdsUI+RBkTCE1GVBd5hMIfNsjOj+YTucPL1hYoWodr2oZsMKDnPuYee0AmGYl8jHzVKgQFSXzgIeWEhG+hOMvTEa4BoruFowcHzbLhym48OlBys47Tlo7WF4tK4s8Z7q8cpbqGsA0NkUqPt8P7Uk3ZaRTUJBbIE08+JgNUUwiJkT4AVSO635aMUYTcIkt+8TVbqOLITfSdDtkxhAmrInfn7FqGlNop3RH1TUcb3xnUOsnHXoreWLlAyWTlWNvOa7hjUH8vUwILPmtG6ixKZdf4i/UIgm551Z5tgE05SJBcR/m7ALagyOyEilWqxGkyf+5jvNycUlk/mBkNHLV45amgUG5zhboVEAEVFYPRa+Nnfzxi+DEdM1ntDURrB5dl4Jbotx67627EbsZm4z2sKUZ4jo/eBj9fI3MUqjTYKtMXBSyuen/m/Uw2Ji9iIQIlWUt/G86kOjQUYFY60g6TxEO4BLouwLZJ0fXxWdolvtMnH90+skJ+I7aFQjGrnwdjGsT0oXA0/vG3W8GhRSokdICVQBq14EFvjcVvI1kEgj7NTtKkB/+UWRtRbRhSA5F25/dHvqAT4ioYy/5sRwYFXMwJmM52I8DpLiaBrp8wTidLNjTpD9KlImbd2r89LS9GG5NeqNWksF9XbEQAMannsu19HC9xf+CCz5FBz5PjpR0+pvybIJLmph94u4I0cdLsbycbbdIaihqeIc593sQv3nvm9VYZMJcgnQWIKmXofSlV4zi6p0wRPvmgeiPo+Lv6DrtPTtcaRLkkyGrg8uvkq2Is9H6me8y8HCXGIze3np22MjWM5r2+UYMia+xY+E3neBC1AZrlN+GJarvf0THgHInLLnE78f+w3mJByFgXMy6OOEs4QI0+F8yxF9GembhqebXYKWGWUkuArnLfV6TZ13v8s1J9IiNObPU0AJjq+wmlg2ChAIs082sh8Q==\",\"format\":\"google-gemini-v1\",\"id\":\"call_4908320\",\"index\":0}],\"reasoning_encrypted\":true},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169245-MjiWKnEhpgHVKzFcOlS0\",\"object\":\"chat.completion.chunk\",\"created\":1791169245,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call_4908320\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"\"}}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169245-MjiWKnEhpgHVKzFcOlS0\",\"object\":\"chat.completion.chunk\",\"created\":1791169245,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169245-MjiWKnEhpgHVKzFcOlS0\",\"object\":\"chat.completion.chunk\",\"created\":1791169245,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"STOP\"}]}\n\ndata: {\"id\":\"gen-1791169245-MjiWKnEhpgHVKzFcOlS0\",\"object\":\"chat.completion.chunk\",\"created\":1791169245,\"model\":\"gemini-3-1-pro-preview\",\"service_tier\":\"provisioned\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"tool_calls\",\"native_finish_reason\":\"STOP\"}]}\n\ndata: {\"id\":\"gen-1791169245-MjiWKnEhpgHVKzFcOlS0\",\"object\":\"chat.completion.chunk\",\"created\":1791169245,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[],\"usage\":{\"prompt_tokens\":96,\"completion_tokens\":169,\"total_tokens\":265,\"prompt_tokens_details\":{\"cached_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":152,\"image_tokens\":0,\"audio_tokens\":0}},\"cost\":{\"usd\":0.002775,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gemini-3-1-pro-preview\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_4908320\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"**Calculating ZEBRA Conversion**\\n\\nI'm currently processing the calculation involving ZEBRA's conversion rate. My next step is to use the `lookup_rate` tool with the code \\\"ZEBRA\\\" to fetch the necessary value, which will then be used in the full equation.\\n\\n\",\"format\":\"google-gemini-v1\",\"index\":0},{\"type\":\"reasoning.encrypted\",\"data\":\"AY89a19Tkd2zuyuB3m6as2cMeOoFk8xFfcBdsUI+RBkTCE1GVBd5hMIfNsjOj+YTucPL1hYoWodr2oZsMKDnPuYee0AmGYl8jHzVKgQFSXzgIeWEhG+hOMvTEa4BoruFowcHzbLhym48OlBys47Tlo7WF4tK4s8Z7q8cpbqGsA0NkUqPt8P7Uk3ZaRTUJBbIE08+JgNUUwiJkT4AVSO635aMUYTcIkt+8TVbqOLITfSdDtkxhAmrInfn7FqGlNop3RH1TUcb3xnUOsnHXoreWLlAyWTlWNvOa7hjUH8vUwILPmtG6ixKZdf4i/UIgm551Z5tgE05SJBcR/m7ALagyOyEilWqxGkyf+5jvNycUlk/mBkNHLV45amgUG5zhboVEAEVFYPRa+Nnfzxi+DEdM1ntDURrB5dl4Jbotx67627EbsZm4z2sKUZ4jo/eBj9fI3MUqjTYKtMXBSyuen/m/Uw2Ji9iIQIlWUt/G86kOjQUYFY60g6TxEO4BLouwLZJ0fXxWdolvtMnH90+skJ+I7aFQjGrnwdjGsT0oXA0/vG3W8GhRSokdICVQBq14EFvjcVvI1kEgj7NTtKkB/+UWRtRbRhSA5F25/dHvqAT4ioYy/5sRwYFXMwJmM52I8DpLiaBrp8wTidLNjTpD9KlImbd2r89LS9GG5NeqNWksF9XbEQAMannsu19HC9xf+CCz5FBz5PjpR0+pvybIJLmph94u4I0cdLsbycbbdIaihqeIc593sQv3nvm9VYZMJcgnQWIKmXofSlV4zi6p0wRPvmgeiPo+Lv6DrtPTtcaRLkkyGrg8uvkq2Is9H6me8y8HCXGIze3np22MjWM5r2+UYMia+xY+E3neBC1AZrlN+GJarvf0THgHInLLnE78f+w3mJByFgXMy6OOEs4QI0+F8yxF9GembhqebXYKWGWUkuArnLfV6TZ13v8s1J9IiNObPU0AJjq+wmlg2ChAIs082sh8Q==\",\"id\":\"call_4908320\",\"format\":\"google-gemini-v1\",\"index\":0}],\"reasoning_content\":\"**Calculating ZEBRA Conversion**\\n\\nI'm currently processing the calculation involving ZEBRA's conversion rate. My next step is to use the `lookup_rate` tool with the code \\\"ZEBRA\\\" to fetch the necessary value, which will then be used in the full equation.\\n\\n\"},{\"role\":\"tool\",\"tool_call_id\":\"call_4908320\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"gen-1791169249-6wlEwbJv7MicDxVsZViY\",\"object\":\"chat.completion.chunk\",\"created\":1791169249,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"400\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169249-6wlEwbJv7MicDxVsZViY\",\"object\":\"chat.completion.chunk\",\"created\":1791169249,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"signature\":\"AY89a1+fQ9YpZF71EoH4pNDO6g5NeqvLRAav/WG6sv9V/5fpifhK6ZDc6m89qgCpOEBhjTJ+a+ux1dFog+eB2nR/G8VVKt1Zr2+2NKXO3Q3ksKE+E51IRRzK/ywpDYpE8AlE6Kb7EG2x/ZVMpy24TOLY+LL8JNsrz8uHBC1PtPtiZe/gP+Kvu0Yqk+VNYv16wAubtiA0oy0c+wyRzd80jRvqCZpBIGPlLAU515MPpjXNGu0A5ft4uu3oGPJHl5G/Ry4Fm5fvWKl759+Jwx9m7d50+vcttr5SpHm//MvNsUTnqjYkraw1S69+9oB57HoBn8d8pwTHpud/6FARun33EjfPW86Xx2fS448QtKc3c9nxSdGyZgTV0TpA63Uz7n0nGd9fRYrghp3lQv5QyoRIgNg+YeBf/WDNzSW2MO6Y2HLFoo3K0vJh1KMfWhjECNxP1Qfjfz+VjwSV7+OzF9CNw23f8R2udRBr1EUYhTu13ckjFLWjCD5fGjvmLhsae3CgmuFV2Rvaxqrf/H79Tz+X9W/Tw8PuQ0E1pCCpiMe1gR1CT+LJPWxjV96DPHQ0r+1lnekrr5NkO7UAZ64HVALHJRpACdJsVhwI1TBi4f89b1by1s7OaWlQTw7thX7CLRV2W3HoMUccJaofszVEfbD8+SaeZtZgyw9mkYvxgYtu3Odxx2YGHgs=\",\"format\":\"google-gemini-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169249-6wlEwbJv7MicDxVsZViY\",\"object\":\"chat.completion.chunk\",\"created\":1791169249,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"STOP\"}]}\n\ndata: {\"id\":\"gen-1791169249-6wlEwbJv7MicDxVsZViY\",\"object\":\"chat.completion.chunk\",\"created\":1791169249,\"model\":\"gemini-3-1-pro-preview\",\"service_tier\":\"provisioned\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"STOP\"}]}\n\ndata: {\"id\":\"gen-1791169249-6wlEwbJv7MicDxVsZViY\",\"object\":\"chat.completion.chunk\",\"created\":1791169249,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[],\"usage\":{\"prompt_tokens\":233,\"completion_tokens\":129,\"total_tokens\":362,\"prompt_tokens_details\":{\"cached_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":126,\"image_tokens\":0,\"audio_tokens\":0}},\"cost\":{\"usd\":0.0025175,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gemini-3-1-pro-preview\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_4908320\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"**Calculating ZEBRA Conversion**\\n\\nI'm currently processing the calculation involving ZEBRA's conversion rate. My next step is to use the `lookup_rate` tool with the code \\\"ZEBRA\\\" to fetch the necessary value, which will then be used in the full equation.\\n\\n\",\"format\":\"google-gemini-v1\",\"index\":0},{\"type\":\"reasoning.encrypted\",\"data\":\"AY89a19Tkd2zuyuB3m6as2cMeOoFk8xFfcBdsUI+RBkTCE1GVBd5hMIfNsjOj+YTucPL1hYoWodr2oZsMKDnPuYee0AmGYl8jHzVKgQFSXzgIeWEhG+hOMvTEa4BoruFowcHzbLhym48OlBys47Tlo7WF4tK4s8Z7q8cpbqGsA0NkUqPt8P7Uk3ZaRTUJBbIE08+JgNUUwiJkT4AVSO635aMUYTcIkt+8TVbqOLITfSdDtkxhAmrInfn7FqGlNop3RH1TUcb3xnUOsnHXoreWLlAyWTlWNvOa7hjUH8vUwILPmtG6ixKZdf4i/UIgm551Z5tgE05SJBcR/m7ALagyOyEilWqxGkyf+5jvNycUlk/mBkNHLV45amgUG5zhboVEAEVFYPRa+Nnfzxi+DEdM1ntDURrB5dl4Jbotx67627EbsZm4z2sKUZ4jo/eBj9fI3MUqjTYKtMXBSyuen/m/Uw2Ji9iIQIlWUt/G86kOjQUYFY60g6TxEO4BLouwLZJ0fXxWdolvtMnH90+skJ+I7aFQjGrnwdjGsT0oXA0/vG3W8GhRSokdICVQBq14EFvjcVvI1kEgj7NTtKkB/+UWRtRbRhSA5F25/dHvqAT4ioYy/5sRwYFXMwJmM52I8DpLiaBrp8wTidLNjTpD9KlImbd2r89LS9GG5NeqNWksF9XbEQAMannsu19HC9xf+CCz5FBz5PjpR0+pvybIJLmph94u4I0cdLsbycbbdIaihqeIc593sQv3nvm9VYZMJcgnQWIKmXofSlV4zi6p0wRPvmgeiPo+Lv6DrtPTtcaRLkkyGrg8uvkq2Is9H6me8y8HCXGIze3np22MjWM5r2+UYMia+xY+E3neBC1AZrlN+GJarvf0THgHInLLnE78f+w3mJByFgXMy6OOEs4QI0+F8yxF9GembhqebXYKWGWUkuArnLfV6TZ13v8s1J9IiNObPU0AJjq+wmlg2ChAIs082sh8Q==\",\"id\":\"call_4908320\",\"format\":\"google-gemini-v1\",\"index\":0}],\"reasoning_content\":\"**Calculating ZEBRA Conversion**\\n\\nI'm currently processing the calculation involving ZEBRA's conversion rate. My next step is to use the `lookup_rate` tool with the code \\\"ZEBRA\\\" to fetch the necessary value, which will then be used in the full equation.\\n\\n\"},{\"role\":\"tool\",\"tool_call_id\":\"call_4908320\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"},{\"role\":\"assistant\",\"content\":\"400\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"format\":\"google-gemini-v1\",\"index\":0,\"signature\":\"AY89a1+fQ9YpZF71EoH4pNDO6g5NeqvLRAav/WG6sv9V/5fpifhK6ZDc6m89qgCpOEBhjTJ+a+ux1dFog+eB2nR/G8VVKt1Zr2+2NKXO3Q3ksKE+E51IRRzK/ywpDYpE8AlE6Kb7EG2x/ZVMpy24TOLY+LL8JNsrz8uHBC1PtPtiZe/gP+Kvu0Yqk+VNYv16wAubtiA0oy0c+wyRzd80jRvqCZpBIGPlLAU515MPpjXNGu0A5ft4uu3oGPJHl5G/Ry4Fm5fvWKl759+Jwx9m7d50+vcttr5SpHm//MvNsUTnqjYkraw1S69+9oB57HoBn8d8pwTHpud/6FARun33EjfPW86Xx2fS448QtKc3c9nxSdGyZgTV0TpA63Uz7n0nGd9fRYrghp3lQv5QyoRIgNg+YeBf/WDNzSW2MO6Y2HLFoo3K0vJh1KMfWhjECNxP1Qfjfz+VjwSV7+OzF9CNw23f8R2udRBr1EUYhTu13ckjFLWjCD5fGjvmLhsae3CgmuFV2Rvaxqrf/H79Tz+X9W/Tw8PuQ0E1pCCpiMe1gR1CT+LJPWxjV96DPHQ0r+1lnekrr5NkO7UAZ64HVALHJRpACdJsVhwI1TBi4f89b1by1s7OaWlQTw7thX7CLRV2W3HoMUccJaofszVEfbD8+SaeZtZgyw9mkYvxgYtu3Odxx2YGHgs=\"}]},{\"role\":\"user\",\"content\":\"Add 7 to your previous final answer. Reply with just the number.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"gen-1791169254-bvEsScICXWimu8xzJFF1\",\"object\":\"chat.completion.chunk\",\"created\":1791169254,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"407\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169254-bvEsScICXWimu8xzJFF1\",\"object\":\"chat.completion.chunk\",\"created\":1791169254,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"signature\":\"AY89a1/t2vneT6Dy8hJmYd66cSC0Fk3bKHK7hJI5vTtBcKe38EQ6JXMwTlxi2K47pfmvruV0B6Ks0CZCF9N7rEOT+fXTleU1WPEAHHJPX7KkncEJK4KNIeJsTzyEjSPn5t8kb/vNruIV2KE2/l0kylycTNOubuO8DGlkKOp478zuwZppB0vFaoLp1byEUtyT/MHdN/KckvvKY99ZB2ksWrc51/5Z7jx7GeXGpTl3jHEKzdLWrfsYYyjPOv4h0ZHN8ANmYMTWpGUm8jqxB0pHQFBVS0U5YhF+fuU6Nk3lUNuLnEydq6UH6VydtTxDjUDALIfHhkasmTRXnL/7X3/z3rLhRXRGhga9lHnSnEhmjdLj9iOn/73wjA4QxnT14P+8F45z4ePO19ZGKr/j5WBvBMem92Yqv9HweDo9IreBa8EbqPwlsnjbXvJB7MuBp2TJd4szb7azQ+zaPU7pf5B3fjKC3K8p26qF0C+TkGd72PezZw662/FMkkC/b88sgDgMfgSK7dMqVvv7swafRzFL1IZzTFoJJ3Ouk/VCKGWCHOx+c2EPtvv+IzGhQ1k9dn44bq953wBamkzDjQWcP46+VKp2PPz5yV9AgGCmdi1SRYNU361FiUGlU40cImBLK1MoIqa/YnYaaXMtK9L7/35uOjyg/hae3REIw5N4Y1fiSx9UCKi7eYviBwrY3kZJYdy4UdsN63ahHb2SO1gfZKAg1G36Gn5TIiYGO4dNWE9c\",\"format\":\"google-gemini-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1791169254-bvEsScICXWimu8xzJFF1\",\"object\":\"chat.completion.chunk\",\"created\":1791169254,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"STOP\"}]}\n\ndata: {\"id\":\"gen-1791169254-bvEsScICXWimu8xzJFF1\",\"object\":\"chat.completion.chunk\",\"created\":1791169254,\"model\":\"gemini-3-1-pro-preview\",\"service_tier\":\"provisioned\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"STOP\"}]}\n\ndata: {\"id\":\"gen-1791169254-bvEsScICXWimu8xzJFF1\",\"object\":\"chat.completion.chunk\",\"created\":1791169254,\"model\":\"gemini-3-1-pro-preview\",\"choices\":[],\"usage\":{\"prompt_tokens\":99,\"completion_tokens\":119,\"total_tokens\":218,\"prompt_tokens_details\":{\"cached_tokens\":0},\"completion_tokens_details\":{\"reasoning_tokens\":116,\"image_tokens\":0,\"audio_tokens\":0}},\"cost\":{\"usd\":0.0020325,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -1,71 +0,0 @@
{
"version": 1,
"metadata": {
"tags": [
"prefix:venice-chat",
"provider:venice",
"protocol:venice-chat",
"tool",
"parallel",
"signature"
],
"name": "venice-chat/gemini-preserves-per-call-signatures-through-parallel-tools",
"recordedAt": "2026-10-05T03:21:54.383Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gemini-3-8-flash\",\"messages\":[{\"role\":\"user\",\"content\":\"Look up private rates for both ZEBRA and MOOSE using two calls to lookup_rate. Then add the rates and multiply by 7. Use parallel calls if possible.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false},\"parallel_tool_calls\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"gcp-1791170510482-itouwq\",\"object\":\"chat.completion.chunk\",\"created\":1791170510,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"tool_calls\":[{\"index\":0,\"id\":\"call_0\",\"type\":\"function\",\"thought_signature\":\"AY89a1/bp+ihzOXpD71E6UhQv6xnBjHTAdPeNHvLEAevtqznKg46D/3qO3e9Qg7wGmccL6IpLV56PwWIT+6pXoeU6ix8JCCk7nlml/Sz2huX/rNFoSRHpB+YAZK07lHP9cbotXy7FPNMaRhsl18sPSJZLlPK0iTGhJa7MmH72r4v/G3/izffMKUPRos6i5qQ52La10FUWSunPigqNnglEvWEC3Yb+d+PnEq5iOPaVCD0awXjhsIk8L0n4+T6jM4epO+whUSSgvH9PGwgmsP197hb7l5YSjQncNX0oF1G4ch3xrFzXyS/noFFy6SlBfzfN0NJmAyP//4oVqjffUJngigTAtboI4HdDwH1fPHuI5FTfmVcUGVVIEjhAEDAAHtS77uW/XdLH4bsSigjYeCzgRTYmHP8bqFb1HjRjl2HK2Pr1y6ch0JSIgtAKv8Kj4R0PM8rIYxxf7LgrsaVWogt4rq2M4HvcUcBi6IhQ/500JUCoqdhlTGMqCFUHkN6P/qAoPGZXmo50OvtNChQ4vXVGYxWp89IRwdcv8vqjIFk75/R6oZG9BsrUaC1JnQN8i3BbYc1bz/lzFCQ1IC/g1Sm+wPhsaPmoVN77101ZAdNMiP4B5Ow7K/UycMunFKWy4xdpaT7Pb+LLNy6GYy9q2TvoRXTq1lJugLH4Q8TQZj5neWZmiDxqzsj0Xvwew7zLwGSMfkpBB4rgx9XWMx5CwqO068qjhmo+0gUkxMug2RHblEGnPO7i0i9fYj9ltDfUpFxN2adDpyk4dwuIeVSlu4N4oHOgjGms5jWLp0MqkyoKFo=\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}]}}]}\n\ndata: {\"id\":\"gcp-1791170510482-itouwq\",\"object\":\"chat.completion.chunk\",\"created\":1791170510,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"index\":1,\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"MOOSE\\\"}\"}}]}}]}\n\ndata: {\"id\":\"gcp-1791170510482-itouwq\",\"object\":\"chat.completion.chunk\",\"created\":1791170510,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"tool_calls\"}]}\n\ndata: {\"id\":\"gcp-1791170510482-itouwq\",\"object\":\"chat.completion.chunk\",\"created\":1791170510,\"model\":\"gemini-3-8-flash\",\"choices\":[],\"usage\":{\"prompt_tokens\":74,\"completion_tokens\":156,\"total_tokens\":230},\"cost\":{\"usd\":0.000800625,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gemini-3-8-flash\",\"messages\":[{\"role\":\"user\",\"content\":\"Look up private rates for both ZEBRA and MOOSE using two calls to lookup_rate. Then add the rates and multiply by 7. Use parallel calls if possible.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_0\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"},\"thought_signature\":\"AY89a1/bp+ihzOXpD71E6UhQv6xnBjHTAdPeNHvLEAevtqznKg46D/3qO3e9Qg7wGmccL6IpLV56PwWIT+6pXoeU6ix8JCCk7nlml/Sz2huX/rNFoSRHpB+YAZK07lHP9cbotXy7FPNMaRhsl18sPSJZLlPK0iTGhJa7MmH72r4v/G3/izffMKUPRos6i5qQ52La10FUWSunPigqNnglEvWEC3Yb+d+PnEq5iOPaVCD0awXjhsIk8L0n4+T6jM4epO+whUSSgvH9PGwgmsP197hb7l5YSjQncNX0oF1G4ch3xrFzXyS/noFFy6SlBfzfN0NJmAyP//4oVqjffUJngigTAtboI4HdDwH1fPHuI5FTfmVcUGVVIEjhAEDAAHtS77uW/XdLH4bsSigjYeCzgRTYmHP8bqFb1HjRjl2HK2Pr1y6ch0JSIgtAKv8Kj4R0PM8rIYxxf7LgrsaVWogt4rq2M4HvcUcBi6IhQ/500JUCoqdhlTGMqCFUHkN6P/qAoPGZXmo50OvtNChQ4vXVGYxWp89IRwdcv8vqjIFk75/R6oZG9BsrUaC1JnQN8i3BbYc1bz/lzFCQ1IC/g1Sm+wPhsaPmoVN77101ZAdNMiP4B5Ow7K/UycMunFKWy4xdpaT7Pb+LLNy6GYy9q2TvoRXTq1lJugLH4Q8TQZj5neWZmiDxqzsj0Xvwew7zLwGSMfkpBB4rgx9XWMx5CwqO068qjhmo+0gUkxMug2RHblEGnPO7i0i9fYj9ltDfUpFxN2adDpyk4dwuIeVSlu4N4oHOgjGms5jWLp0MqkyoKFo=\"},{\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"MOOSE\\\"}\"}}]},{\"role\":\"tool\",\"tool_call_id\":\"call_0\",\"content\":\"{\\\"rate\\\":23}\"},{\"role\":\"tool\",\"tool_call_id\":\"call_1\",\"content\":\"{\\\"rate\\\":31}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false},\"parallel_tool_calls\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"The retrieved rates are:\\n- **\"}}]}\n\ndata: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"ZEBRA:** 23\\n- **MOOSE:** 31\\n\\n### Calculation:\\n1. **Add\"}}]}\n\ndata: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\" the rates:** \\n $$23 + 31 = 54$$\\n\\n2. **Multiply by 7:** \"}}]}\n\ndata: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\\n $$54 \\\\times 7 = 378$$\\n\\nThe final result is **378\"}}]}\n\ndata: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"**.\"}}]}\n\ndata: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"thought_signature\":\"AY89a18wXUWI7m5/5GGqxfoJDqE4MU2X2SR/ZQMCX+JQ/clHQynyEjplK4a8w9tXAi8uuxYPVxYj1ULcb83zUQBsr77tSkJN5R0J2Jgc2HzYtmTW7OYHoFQqmMESnDjvDiO51y3xpXGbVkqRlXwfmap+Tpivfx45hiXlns/hObEQo1gE70NzD7d8CQOseYVz2N7MPMmGDqpAy+5SAHXGAcQ9K2ufHNUdfWM/uUi+sGUcDhGnjPXPlzIuYk7IjUQXNAH9FFqo19dBQPhE106C3aI69QAuMvyZZ/no4ou9XSOUXrstjzQwUM+TOJXsab80F1+C6DghvYm0UZSb827xCKPtj7oCQwtwg+f4sWELhflp42IL3URo2UxpZaDNpt7PYXmmcCvKAN7/PTirCijNWMtlDL0LDbiKj+DfOFEuS2VxVq+yQgWRbKJZF7bl8BCSe7+Qab0bAsu0ZJXKBlxs46ulaXn5cc933y/Yv7O0XGHbVULMzeEpZndzkHqZYs5D+ve+CJFfjWsVk1NKrTdXqt11Ov470+MCpSlHTXdjug7oUd0A0b5AH+bF4yAZAGj4snJ7hi04EQ4Ch7rASB3OB+YB+8SnStAGf0XuimNgY2wzR68qaQ==\"}}]}\n\ndata: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: {\"id\":\"gcp-1791170512529-w6lmsz\",\"object\":\"chat.completion.chunk\",\"created\":1791170512,\"model\":\"gemini-3-8-flash\",\"choices\":[],\"usage\":{\"prompt_tokens\":206,\"completion_tokens\":208,\"total_tokens\":414},\"cost\":{\"usd\":0.001168125,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"gemini-3-8-flash\",\"messages\":[{\"role\":\"user\",\"content\":\"Look up private rates for both ZEBRA and MOOSE using two calls to lookup_rate. Then add the rates and multiply by 7. Use parallel calls if possible.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_0\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"},\"thought_signature\":\"AY89a1/bp+ihzOXpD71E6UhQv6xnBjHTAdPeNHvLEAevtqznKg46D/3qO3e9Qg7wGmccL6IpLV56PwWIT+6pXoeU6ix8JCCk7nlml/Sz2huX/rNFoSRHpB+YAZK07lHP9cbotXy7FPNMaRhsl18sPSJZLlPK0iTGhJa7MmH72r4v/G3/izffMKUPRos6i5qQ52La10FUWSunPigqNnglEvWEC3Yb+d+PnEq5iOPaVCD0awXjhsIk8L0n4+T6jM4epO+whUSSgvH9PGwgmsP197hb7l5YSjQncNX0oF1G4ch3xrFzXyS/noFFy6SlBfzfN0NJmAyP//4oVqjffUJngigTAtboI4HdDwH1fPHuI5FTfmVcUGVVIEjhAEDAAHtS77uW/XdLH4bsSigjYeCzgRTYmHP8bqFb1HjRjl2HK2Pr1y6ch0JSIgtAKv8Kj4R0PM8rIYxxf7LgrsaVWogt4rq2M4HvcUcBi6IhQ/500JUCoqdhlTGMqCFUHkN6P/qAoPGZXmo50OvtNChQ4vXVGYxWp89IRwdcv8vqjIFk75/R6oZG9BsrUaC1JnQN8i3BbYc1bz/lzFCQ1IC/g1Sm+wPhsaPmoVN77101ZAdNMiP4B5Ow7K/UycMunFKWy4xdpaT7Pb+LLNy6GYy9q2TvoRXTq1lJugLH4Q8TQZj5neWZmiDxqzsj0Xvwew7zLwGSMfkpBB4rgx9XWMx5CwqO068qjhmo+0gUkxMug2RHblEGnPO7i0i9fYj9ltDfUpFxN2adDpyk4dwuIeVSlu4N4oHOgjGms5jWLp0MqkyoKFo=\"},{\"id\":\"call_1\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"MOOSE\\\"}\"}}]},{\"role\":\"tool\",\"tool_call_id\":\"call_0\",\"content\":\"{\\\"rate\\\":23}\"},{\"role\":\"tool\",\"tool_call_id\":\"call_1\",\"content\":\"{\\\"rate\\\":31}\"},{\"role\":\"assistant\",\"content\":\"The retrieved rates are:\\n- **ZEBRA:** 23\\n- **MOOSE:** 31\\n\\n### Calculation:\\n1. **Add the rates:** \\n $$23 + 31 = 54$$\\n\\n2. **Multiply by 7:** \\n $$54 \\\\times 7 = 378$$\\n\\nThe final result is **378**.\",\"thought_signature\":\"AY89a18wXUWI7m5/5GGqxfoJDqE4MU2X2SR/ZQMCX+JQ/clHQynyEjplK4a8w9tXAi8uuxYPVxYj1ULcb83zUQBsr77tSkJN5R0J2Jgc2HzYtmTW7OYHoFQqmMESnDjvDiO51y3xpXGbVkqRlXwfmap+Tpivfx45hiXlns/hObEQo1gE70NzD7d8CQOseYVz2N7MPMmGDqpAy+5SAHXGAcQ9K2ufHNUdfWM/uUi+sGUcDhGnjPXPlzIuYk7IjUQXNAH9FFqo19dBQPhE106C3aI69QAuMvyZZ/no4ou9XSOUXrstjzQwUM+TOJXsab80F1+C6DghvYm0UZSb827xCKPtj7oCQwtwg+f4sWELhflp42IL3URo2UxpZaDNpt7PYXmmcCvKAN7/PTirCijNWMtlDL0LDbiKj+DfOFEuS2VxVq+yQgWRbKJZF7bl8BCSe7+Qab0bAsu0ZJXKBlxs46ulaXn5cc933y/Yv7O0XGHbVULMzeEpZndzkHqZYs5D+ve+CJFfjWsVk1NKrTdXqt11Ov470+MCpSlHTXdjug7oUd0A0b5AH+bF4yAZAGj4snJ7hi04EQ4Ch7rASB3OB+YB+8SnStAGf0XuimNgY2wzR68qaQ==\"},{\"role\":\"user\",\"content\":\"Add 7 to your previous final answer. Reply with just the number.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"none\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"effort\":\"high\"},\"venice_parameters\":{\"include_venice_system_prompt\":false},\"parallel_tool_calls\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"gcp-1791170514341-j5gh1h\",\"object\":\"chat.completion.chunk\",\"created\":1791170514,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"385\"}}]}\n\ndata: {\"id\":\"gcp-1791170514341-j5gh1h\",\"object\":\"chat.completion.chunk\",\"created\":1791170514,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{\"thought_signature\":\"AY89a1/DMIB/2qixC5QVQQH1tA2QiQFpEsXnIDd7wlCgwuYQXBGWgSsP1bpTZJUJM/yqKdQh96+/c6c28VMbW5iovCDxxeK56KfmSBSBIPtNW8BUHHLxQw9vdsxQvQyHcGW6rLb4W3gElqx6JFmNkTMvijUS9QDgkPlPipirbv7pqLejFyQ040GD4IVhP8ovyR49EJm61W0JvJOPFT30/gVvaKA6RnRgTKGiHrZWVqLxhKVkb0gC7jI9CNNASOYH67uelFQ2trbcf5Qbhx4q93vAErFsBzvzliSuaELDrxZAF9VCNIiWz4AUtEWI1FLCMYxkgNry/K+iNpwcXu8BxABETbNkEb722wjpt1A2EZK/5qhqKQvUOq+WgSCPCLoI1zfUiGN6UV9D/QGtyS2Iyt3AYlvKjZVVzP217ItWSDacGzigLd4t+4MMW7Dvbs7ffBvBV+EzMCP1dnsLkKCxicfvT3e2EgY+liDSaSZG3sE7Lm4WKCYbI4rrBLMupERSaFfAHWdTlea5HeM=\"}}]}\n\ndata: {\"id\":\"gcp-1791170514341-j5gh1h\",\"object\":\"chat.completion.chunk\",\"created\":1791170514,\"model\":\"gemini-3-8-flash\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\ndata: {\"id\":\"gcp-1791170514341-j5gh1h\",\"object\":\"chat.completion.chunk\",\"created\":1791170514,\"model\":\"gemini-3-8-flash\",\"choices\":[],\"usage\":{\"prompt_tokens\":429,\"completion_tokens\":83,\"total_tokens\":512},\"cost\":{\"usd\":0.00079125,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -1,75 +0,0 @@
{
"version": 1,
"metadata": {
"model": "openai-gpt-54-mini",
"tags": [
"prefix:venice-chat",
"provider:venice",
"protocol:venice-chat",
"reasoning",
"tool",
"tool-loop",
"continuation",
"usage",
"encrypted"
],
"name": "venice-chat/openai-gpt-54-mini-preserves-reasoning-through-a-tool-loop-and-follow-up",
"recordedAt": "2026-10-05T03:01:10.096Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"openai-gpt-54-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for a code. Only this tool knows the rate.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"code\":{\"type\":\"string\",\"enum\":[\"ZEBRA\",\"MOOSE\"]}},\"required\":[\"code\"],\"additionalProperties\":false},\"strict\":false}}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"**Calculating private data**\\n\\nI\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" need\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" to\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" follow\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" the\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" developer\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"'s\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" instructions\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" and\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" use\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" the\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" provided\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" tool\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" to\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" handle\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" private\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" data\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\".\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312efd07487d28fc51d6572174c85\",\"object\":\"chat.completion.chunk\",\"created\":1791169264,\"model\":\"openai-gpLine truncated
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"openai-gpt-54-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_onp7EArhOgXu0hGRF6Y5WHLu\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_details\":[{\"type\":\"reasoning.encrypted\",\"data\":\"gAAAAABqwxLxEN4vtmI2WcZ_aDDttdmPwAHgnV2gLZBo_f_mmTdGm4oZzE2JVzcj72AeXHyABmmo2VQN4nXaZK9GSSUZRlYL8mECMxKTj9AMgL8zlnuN5WDexxU-e9SduGP36RN9nH56-go4dz8eu5DEMvMf_TMoTnxz5AnC_UWbty63Vp8jEgkD-DoRjd0cg9LjthP_FLyMvP2Wj9qMJxEuO7Mw2_Pu4I3YeChnygLvmRfRR2QkamL5vjb9yHkl0Lb502vEGB2DW_czNEpHxisifvwRBjA7vk0mbn2dlhiR80aNHO71hC-QqrtUfPXx2HIbVLfbPdJf0saI0wD7ZuCJSQCBfB7fMCnj2ZLQeXa67lgXwsZDLBg4HmSJFvsMinx1IHIjUk7iebsKvmYx40cjqpXL3wj7htZWk2hO9OyGGUwlRsn5THdhTsgwg2SPbuHxb-sLPQnEFZ7bRp5lsVm31X2dlHDa1uZ7KLJZyy9g2CLIKsBwFFoCK7jY3HBXfL21ac5TAkOzVmz-XKl6OMPSl-PSdByOO15uHHpyOcJvJvuaea5Tj1nihtuDlhUP5EQPi7MiWpJMFo9AhDCafAbaQOEF5W8AQPeb_GXvPTzhySRAC8PxrhraEcZ4fjpjLqkv6mi_A1YHwbHEHHWjcK5VEKc3dadlIM_s6djclFBocVXrvnQQElVhWBDM-RtK9sgR07BFj5LVk6fYvg0b6gQnbDuQH18jhtPoj0SX0lN921XEQ1ebMCWGLANND2ew_wyNShYouJohefLULicWG57xh_yV1KEG1ufBedq2CPvCHA9Hm5UssbeJx5yBriyUA2Yh4D7j2UBT_KDWEHydDLQ4mWEyLTfQn0ZB3ZOqkU79z3HpK2w_VHUnkl5nEYEU2bmF5lsG33B4gtbQIBtZT76-fizXsvVZx__taOWXg96VMi3CD3DY0TS76B4U92KFmAFJ4aeiM8bkPuzf1Qd202EWE39CotUDoxAM6mzQu0NObTDnGQE776P6kzUoIKUmP8sDwYG5xot8ZTuXcBYysvj4-pFC_fvqMhsdfgonbke78SIjHtmePsOkfHBnCjSSUFKEbaM0nm8jJ6nYVwiYFuXBKD31QD462TAC7ORVIn32sWSgV2t3Sd7vq4ws2Yy7nUoILRAt_NYQUEXt7_R8J5YWj1L6Ruagk7Vtt-CAWxT6FhVyU4JhMb6-tJrYHgdmtunYrjpHBiOVdQR25aOmLbc_WlDvpO4azcRazpgkASAjPaaDycYceKcheiyUDBgFrvcSYrS1fJb6YZrTzOKw1Ztd0YsklS4nc3jffLNLrBQVvnkajj0OHjGcW8wkP93TCMJyt8tfIH0Rd5hIZLY5EqUWj9yZgXO7CuDKQZIEI0oZXef3mjfQqH8Q9rZsSgS3DI3mCF3CN003R_ZKKLGWkG8VzwlvdocNxaV_Z-ujp_O0YMyOcmEoTJI-j3JP3nefNboyP7EWND6VWDjJbqg64dvu0PK0EW5L0lnAu0l-WVb2qmXLYao_7bOlD8JLFZOyUgHAWmetmnm0NuxzevJEVBkNUu1jQatPzf2Jzf9SGKM_3bM_z0TM5McCZ2Pikzwp96QlaiEEWV_j2g02PT5Y3td-lfpoKxoFD5tltGNY9TlPzK-4a9Iz0hloiCprxWMLGKw2JzctrPF1xr42PKGNgPE84xAQ3sQ4mQ==\",\"id\":\"rs_014406f0f45e19c6016ac312f01d2487d2a5f2bdb7fbc42f76\",\"format\":\"openai-responses-v1\",\"index\":0}],\"reasoning_content\":\"**Calculating private data**\\n\\nI need to follow the developer's instructions and use the provided tool to handle private data. First, I should look up the rate for \\\"ZEBRA\\\" before I answer. After that, I’ll multiply the rate by 17 and then add 9 to get the required number. It’s important to give only the requested number and avoid making another tool call. I’ll likely use the commentary channel for this tool call.\\n\\n__ENCRYPTED_REASONING__id=rs_014406f0f45e19c6016ac312f01d2487d2a5f2bdb7fbc42f76\\ngAAAAABqwxLxEN4vtmI2WcZ_aDDttdmPwAHgnV2gLZBo_f_mmTdGm4oZzE2JVzcj72AeXHyABmmo2VQN4nXaZK9GSSUZRlYL8mECMxKTj9AMgL8zlnuN5WDexxU-e9SduGP36RN9nH56-go4dz8eu5DEMvMf_TMoTnxz5AnC_UWbty63Vp8jEgkD-DoRjd0cg9LjthP_FLyMvP2Wj9qMJxEuO7Mw2_Pu4I3YeChnygLvmRfRR2QkamL5vjb9yHkl0Lb502vEGB2DW_czNEpHxisifvwRBjA7vk0mbn2dlhiR80aNHO71hC-QqrtUfPXx2HIbVLfbPdJf0saI0wD7ZuCJSQCBfB7fMCnj2ZLQeXa67lgXwsZDLBg4HmSJFvsMinx1IHIjUk7iebsKvmYx40cjqpXL3wj7htZWk2hO9OyGGUwlRsn5THdhTsgwg2SPbuHxb-sLPQnEFZ7bRp5lsVm31X2dlHDa1uZ7KLJZyy9g2CLIKsBwFFoCK7jY3HBXfL21ac5TAkOzVmz-XKl6OMPSl-PSdByOO15uHHpyOcJvJvuaea5Tj1nihtuDlhUP5EQPi7MiWpJMFo9AhDCafAbaQOEF5W8AQPeb_GXvPTzhySRAC8PxrhraEcZ4fjpjLqkv6mi_A1YHwbHEHHWjcK5VEKc3dadlIM_s6djclFBocVXrvnQQElVhWBDM-RtK9sgR07BFj5LVk6fYvg0b6gQnbDuQH18jhtPoj0SX0lN921XEQ1ebMCWGLANND2ew_wyNShYouJohefLULicWG57xh_yV1KEG1ufBedq2CPvCHA9Hm5UssbeJx5yBriyUA2Yh4D7j2UBT_KDWEHydDLQ4mWEyLTfQn0ZB3ZOqkU79z3HpK2w_VHUnkl5nEYEU2bmF5lsG33B4gtbQIBtZT76-fizXsvVZx__taOWXg96VMi3CD3DY0TS76B4U92KFmAFJ4aeiM8bkPuzf1Qd202EWE39CotUDoxAM6mzQu0NObTDnGQE776P6kzUoIKUmP8sDwYG5xot8ZTuXcBYysvj4-pFC_fvqMhsdfgonbke78SIjHtmePsOkfHBnCjSSUFKEbaM0nm8jJ6nYVwiYFuXBKD31QD462TAC7ORVIn32sWSgV2t3Sd7vq4ws2Yy7nUoILRAt_NYQUEXt7_R8J5YWj1L6Ruagk7Vtt-CAWxT6FhVyU4JhMb6-tJrYHgdmtunYrjpHBiOVdQR25aOmLbc_WlDvpO4azcRazpgkASAjPaaDycYceKcheiyUDBgFrvcSYrS1fJb6YZrTzOKw1Ztd0YsklS4nc3jffLNLrBQVvnkajj0OHjGcW8wkP93TCMJyt8tfIH0Rd5hIZLY5EqUWj9yZgXO7CuDKQZIEI0oZXef3mjfQqH8Q9rZsSgS3DI3mCF3CN003R_ZKKLGWkG8VzwlvdocNxaV_Z-ujp_O0YMyOcmEoTJI-j3JP3nefNboyP7EWND6VWDjJbqg64dvu0PK0EW5L0lnAu0l-WVb2qmXLYao_7bOlD8JLFZOyUgHAWmetmnm0NuxzevJEVBkNUu1jQatPzf2Jzf9SGKM_3bM_z0TM5McCZ2Pikzwp96QlaiEEWV_j2g02PT5Y3td-lfpoKxoFD5tltGNY9TlPzK-4a9Iz0hloiCprxWMLGKw2JzctrPF1xr42PKGNgPE84xAQ3sQ4mQ==\"},{\"role\":\"tool\",\"tool_call_id\":\"call_onp7EArhOgXu0hGRF6Y5WHLu\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"description\":\"Retrieve the private conversion rate for aLine truncated
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"**Calculating a simple math problem**\\n\\nI'm\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" working\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" through\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" the\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" math\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\":\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" first\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\",\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" I\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169266,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" multiply\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" 23\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" by\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" 17\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\",\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" and\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" that\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" gives\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" me\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f2137487d2aa3a1f180cbf07eb\",\"object\":\"chat.completion.chunk\",\"created\":1791169267,\"model\":\"openai-gpt-54-mini\"Line truncated
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"openai-gpt-54-mini\",\"messages\":[{\"role\":\"system\",\"content\":\"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.\"},{\"role\":\"user\",\"content\":\"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.\"},{\"role\":\"assistant\",\"content\":null,\"tool_calls\":[{\"id\":\"call_onp7EArhOgXu0hGRF6Y5WHLu\",\"type\":\"function\",\"function\":{\"name\":\"lookup_rate\",\"arguments\":\"{\\\"code\\\":\\\"ZEBRA\\\"}\"}}],\"reasoning_details\":[{\"type\":\"reasoning.encrypted\",\"data\":\"gAAAAABqwxLxEN4vtmI2WcZ_aDDttdmPwAHgnV2gLZBo_f_mmTdGm4oZzE2JVzcj72AeXHyABmmo2VQN4nXaZK9GSSUZRlYL8mECMxKTj9AMgL8zlnuN5WDexxU-e9SduGP36RN9nH56-go4dz8eu5DEMvMf_TMoTnxz5AnC_UWbty63Vp8jEgkD-DoRjd0cg9LjthP_FLyMvP2Wj9qMJxEuO7Mw2_Pu4I3YeChnygLvmRfRR2QkamL5vjb9yHkl0Lb502vEGB2DW_czNEpHxisifvwRBjA7vk0mbn2dlhiR80aNHO71hC-QqrtUfPXx2HIbVLfbPdJf0saI0wD7ZuCJSQCBfB7fMCnj2ZLQeXa67lgXwsZDLBg4HmSJFvsMinx1IHIjUk7iebsKvmYx40cjqpXL3wj7htZWk2hO9OyGGUwlRsn5THdhTsgwg2SPbuHxb-sLPQnEFZ7bRp5lsVm31X2dlHDa1uZ7KLJZyy9g2CLIKsBwFFoCK7jY3HBXfL21ac5TAkOzVmz-XKl6OMPSl-PSdByOO15uHHpyOcJvJvuaea5Tj1nihtuDlhUP5EQPi7MiWpJMFo9AhDCafAbaQOEF5W8AQPeb_GXvPTzhySRAC8PxrhraEcZ4fjpjLqkv6mi_A1YHwbHEHHWjcK5VEKc3dadlIM_s6djclFBocVXrvnQQElVhWBDM-RtK9sgR07BFj5LVk6fYvg0b6gQnbDuQH18jhtPoj0SX0lN921XEQ1ebMCWGLANND2ew_wyNShYouJohefLULicWG57xh_yV1KEG1ufBedq2CPvCHA9Hm5UssbeJx5yBriyUA2Yh4D7j2UBT_KDWEHydDLQ4mWEyLTfQn0ZB3ZOqkU79z3HpK2w_VHUnkl5nEYEU2bmF5lsG33B4gtbQIBtZT76-fizXsvVZx__taOWXg96VMi3CD3DY0TS76B4U92KFmAFJ4aeiM8bkPuzf1Qd202EWE39CotUDoxAM6mzQu0NObTDnGQE776P6kzUoIKUmP8sDwYG5xot8ZTuXcBYysvj4-pFC_fvqMhsdfgonbke78SIjHtmePsOkfHBnCjSSUFKEbaM0nm8jJ6nYVwiYFuXBKD31QD462TAC7ORVIn32sWSgV2t3Sd7vq4ws2Yy7nUoILRAt_NYQUEXt7_R8J5YWj1L6Ruagk7Vtt-CAWxT6FhVyU4JhMb6-tJrYHgdmtunYrjpHBiOVdQR25aOmLbc_WlDvpO4azcRazpgkASAjPaaDycYceKcheiyUDBgFrvcSYrS1fJb6YZrTzOKw1Ztd0YsklS4nc3jffLNLrBQVvnkajj0OHjGcW8wkP93TCMJyt8tfIH0Rd5hIZLY5EqUWj9yZgXO7CuDKQZIEI0oZXef3mjfQqH8Q9rZsSgS3DI3mCF3CN003R_ZKKLGWkG8VzwlvdocNxaV_Z-ujp_O0YMyOcmEoTJI-j3JP3nefNboyP7EWND6VWDjJbqg64dvu0PK0EW5L0lnAu0l-WVb2qmXLYao_7bOlD8JLFZOyUgHAWmetmnm0NuxzevJEVBkNUu1jQatPzf2Jzf9SGKM_3bM_z0TM5McCZ2Pikzwp96QlaiEEWV_j2g02PT5Y3td-lfpoKxoFD5tltGNY9TlPzK-4a9Iz0hloiCprxWMLGKw2JzctrPF1xr42PKGNgPE84xAQ3sQ4mQ==\",\"id\":\"rs_014406f0f45e19c6016ac312f01d2487d2a5f2bdb7fbc42f76\",\"format\":\"openai-responses-v1\",\"index\":0}],\"reasoning_content\":\"**Calculating private data**\\n\\nI need to follow the developer's instructions and use the provided tool to handle private data. First, I should look up the rate for \\\"ZEBRA\\\" before I answer. After that, I’ll multiply the rate by 17 and then add 9 to get the required number. It’s important to give only the requested number and avoid making another tool call. I’ll likely use the commentary channel for this tool call.\\n\\n__ENCRYPTED_REASONING__id=rs_014406f0f45e19c6016ac312f01d2487d2a5f2bdb7fbc42f76\\ngAAAAABqwxLxEN4vtmI2WcZ_aDDttdmPwAHgnV2gLZBo_f_mmTdGm4oZzE2JVzcj72AeXHyABmmo2VQN4nXaZK9GSSUZRlYL8mECMxKTj9AMgL8zlnuN5WDexxU-e9SduGP36RN9nH56-go4dz8eu5DEMvMf_TMoTnxz5AnC_UWbty63Vp8jEgkD-DoRjd0cg9LjthP_FLyMvP2Wj9qMJxEuO7Mw2_Pu4I3YeChnygLvmRfRR2QkamL5vjb9yHkl0Lb502vEGB2DW_czNEpHxisifvwRBjA7vk0mbn2dlhiR80aNHO71hC-QqrtUfPXx2HIbVLfbPdJf0saI0wD7ZuCJSQCBfB7fMCnj2ZLQeXa67lgXwsZDLBg4HmSJFvsMinx1IHIjUk7iebsKvmYx40cjqpXL3wj7htZWk2hO9OyGGUwlRsn5THdhTsgwg2SPbuHxb-sLPQnEFZ7bRp5lsVm31X2dlHDa1uZ7KLJZyy9g2CLIKsBwFFoCK7jY3HBXfL21ac5TAkOzVmz-XKl6OMPSl-PSdByOO15uHHpyOcJvJvuaea5Tj1nihtuDlhUP5EQPi7MiWpJMFo9AhDCafAbaQOEF5W8AQPeb_GXvPTzhySRAC8PxrhraEcZ4fjpjLqkv6mi_A1YHwbHEHHWjcK5VEKc3dadlIM_s6djclFBocVXrvnQQElVhWBDM-RtK9sgR07BFj5LVk6fYvg0b6gQnbDuQH18jhtPoj0SX0lN921XEQ1ebMCWGLANND2ew_wyNShYouJohefLULicWG57xh_yV1KEG1ufBedq2CPvCHA9Hm5UssbeJx5yBriyUA2Yh4D7j2UBT_KDWEHydDLQ4mWEyLTfQn0ZB3ZOqkU79z3HpK2w_VHUnkl5nEYEU2bmF5lsG33B4gtbQIBtZT76-fizXsvVZx__taOWXg96VMi3CD3DY0TS76B4U92KFmAFJ4aeiM8bkPuzf1Qd202EWE39CotUDoxAM6mzQu0NObTDnGQE776P6kzUoIKUmP8sDwYG5xot8ZTuXcBYysvj4-pFC_fvqMhsdfgonbke78SIjHtmePsOkfHBnCjSSUFKEbaM0nm8jJ6nYVwiYFuXBKD31QD462TAC7ORVIn32sWSgV2t3Sd7vq4ws2Yy7nUoILRAt_NYQUEXt7_R8J5YWj1L6Ruagk7Vtt-CAWxT6FhVyU4JhMb6-tJrYHgdmtunYrjpHBiOVdQR25aOmLbc_WlDvpO4azcRazpgkASAjPaaDycYceKcheiyUDBgFrvcSYrS1fJb6YZrTzOKw1Ztd0YsklS4nc3jffLNLrBQVvnkajj0OHjGcW8wkP93TCMJyt8tfIH0Rd5hIZLY5EqUWj9yZgXO7CuDKQZIEI0oZXef3mjfQqH8Q9rZsSgS3DI3mCF3CN003R_ZKKLGWkG8VzwlvdocNxaV_Z-ujp_O0YMyOcmEoTJI-j3JP3nefNboyP7EWND6VWDjJbqg64dvu0PK0EW5L0lnAu0l-WVb2qmXLYao_7bOlD8JLFZOyUgHAWmetmnm0NuxzevJEVBkNUu1jQatPzf2Jzf9SGKM_3bM_z0TM5McCZ2Pikzwp96QlaiEEWV_j2g02PT5Y3td-lfpoKxoFD5tltGNY9TlPzK-4a9Iz0hloiCprxWMLGKw2JzctrPF1xr42PKGNgPE84xAQ3sQ4mQ==\"},{\"role\":\"tool\",\"tool_call_id\":\"call_onp7EArhOgXu0hGRF6Y5WHLu\",\"content\":\"{\\\"code\\\":\\\"ZEBRA\\\",\\\"rate\\\":23}\"},{\"role\":\"assistant\",\"content\":\"400\",\"reasoning_details\":[{\"type\":\"reasoning.encrypted\",\"data\":\"gAAAAABqwxL0Nl6Jl5EZuOLine truncated
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\"**Calculating a simple addition**\\n\\nThe\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" user\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" wants\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" me\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" to\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" add\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" 7\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" to\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" the\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" previous\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" final\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" answer\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\",\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" which\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" was\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" 400\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\".\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"choices\":[{\"index\":0,\"delta\":{\"reasoning_content\":\" This\"},\"finish_reason\":null}]}\n\ndata: {\"id\":\"resp_014406f0f45e19c6016ac312f49b9c87d2afc60121b8910612\",\"object\":\"chat.completion.chunk\",\"created\":1791169269,\"model\":\"openai-gpt-54-mini\",\"Line truncated
}
}
]
}
@@ -1,35 +0,0 @@
{
"version": 1,
"metadata": {
"tags": [
"prefix:venice-chat",
"provider:venice",
"protocol:venice-chat",
"text",
"reasoning",
"toggle"
],
"name": "venice-chat/qwen-disables-reasoning",
"recordedAt": "2026-10-05T02:59:04.614Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"qwen3-6-27b\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 23 multiplied by 17 plus 9? Reply with just the number.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":4096,\"reasoning\":{\"enabled\":false},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"service_tier\":null,\"id\":\"chatcmpl-RxYUpStKYw7YgEZcXjsb2dOt\",\"object\":\"chat.completion.chunk\",\"created\":1791169144,\"model\":\"qwen3-6-27b\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxYUpStKYw7YgEZcXjsb2dOt\",\"object\":\"chat.completion.chunk\",\"created\":1791169144,\"model\":\"qwen3-6-27b\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxYUpStKYw7YgEZcXjsb2dOt\",\"object\":\"chat.completion.chunk\",\"created\":1791169144,\"model\":\"qwen3-6-27b\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"4\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxYUpStKYw7YgEZcXjsb2dOt\",\"object\":\"chat.completion.chunk\",\"created\":1791169144,\"model\":\"qwen3-6-27b\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"0\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxYUpStKYw7YgEZcXjsb2dOt\",\"object\":\"chat.completion.chunk\",\"created\":1791169144,\"model\":\"qwen3-6-27b\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"0\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":null}],\"usage\":null}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxYUpStKYw7YgEZcXjsb2dOt\",\"object\":\"chat.completion.chunk\",\"created\":1791169144,\"model\":\"qwen3-6-27b\",\"choices\":[{\"index\":0,\"delta\":{\"role\":null,\"content\":\"\",\"reasoning_content\":null,\"tool_calls\":null},\"logprobs\":null,\"finish_reason\":\"stop\"}]}\n\ndata: {\"service_tier\":\"default\",\"id\":\"chatcmpl-RxYUpStKYw7YgEZcXjsb2dOt\",\"object\":\"chat.completion.chunk\",\"created\":1791169144,\"model\":\"qwen3-6-27b\",\"choices\":[],\"usage\":{\"prompt_tokens\":32,\"completion_tokens\":4,\"total_tokens\":36},\"cost\":{\"usd\":0.0000234,\"diem\":0}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -1,33 +0,0 @@
{
"version": 1,
"metadata": {
"tags": [
"prefix:venice-chat",
"provider:venice",
"protocol:venice-chat",
"error"
],
"name": "venice-chat/surfaces-venice-model-errors",
"recordedAt": "2026-10-05T02:59:11.768Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.venice.ai/api/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"no-such-model-xyz\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"venice_parameters\":{\"include_venice_system_prompt\":false}}"
},
"response": {
"status": 404,
"headers": {
"content-type": "application/json; charset=utf-8"
},
"body": "{\"error\":\"Specified model not found: no-such-model-xyz. Did you mean: z-ai-glm-5-3, z-ai-glm-5-3-flash, z-ai-glm-5-turbo?\"}"
}
}
]
}
@@ -1,26 +0,0 @@
import { LLM } from "../../src/index.js"
import { Venice } from "../../src/providers.js"
LLM.request({
model: Venice.chat("qwen3-6-27b"),
providerOptions: { reasoningEffort: "high", reasoning: { summary: "concise" }, veniceParameters: { includeVeniceSystemPrompt: false } },
})
LLM.request({
model: Venice.configure({ providerOptions: { reasoningEffort: "future-effort" } }).model("future-model"),
providerOptions: { reasoning: { enabled: false }, promptCacheRetention: "future-retention", parallelToolCalls: false },
})
LLM.request({
model: Venice.chat("qwen3-6-27b"),
// @ts-expect-error Thinking toggles are boolean.
providerOptions: { reasoning: { enabled: "false" } },
})
LLM.request({
model: Venice.chat("qwen3-6-27b"),
// @ts-expect-error Venice uses nested reasoning, not Anthropic thinking controls.
providerOptions: { thinking: { type: "disabled" } },
})
LLM.request({
model: Venice.chat("qwen3-6-27b"),
// @ts-expect-error Venice's system-prompt toggle is boolean.
providerOptions: { veniceParameters: { includeVeniceSystemPrompt: "false" } },
})
@@ -431,115 +431,42 @@ describe("Anthropic Messages route", () => {
(yield* compileRequest(
LLM.request({
model: opus48,
messages: [
Message.user("Start."),
Message.assistant("One."),
Message.system("Update."),
Message.assistant("Two."),
],
messages: [Message.user("Before."), Message.system("One."), Message.system("Two.")],
cache: "none",
}),
)).body.messages,
).toEqual([
{ role: "user", content: [{ type: "text", text: "Start." }] },
{ role: "assistant", content: [{ type: "text", text: "One." }] },
{ role: "user", content: [{ type: "text", text: "<system-update>\nUpdate.\n</system-update>" }] },
{ role: "assistant", content: [{ type: "text", text: "Two." }] },
{
role: "user",
content: [
{ type: "text", text: "Before." },
{ type: "text", text: "<system-update>\nOne.\n</system-update>" },
{ type: "text", text: "<system-update>\nTwo.\n</system-update>" },
],
},
])
}),
)
it.effect("moves system updates to the next assistant turn and sends consecutive updates together", () =>
Effect.gen(function* () {
const lower = (messages: ReadonlyArray<Message>) =>
compileRequest(LLM.request({ model: opus48, messages: [...messages], cache: "none" })).pipe(
Effect.map((prepared) => prepared.body.messages),
)
const system = (text: string) => ({ role: "system", content: [{ type: "text", text, cache_control: undefined }] })
const user = (text: string) => ({ role: "user", content: [{ type: "text", text }] })
const assistant = (text: string) => ({ role: "assistant", content: [{ type: "text", text }] })
expect(
yield* lower([
Message.user("Fix it."),
Message.assistant("Done."),
Message.system("Update."),
Message.user("Next."),
]),
).toEqual([user("Fix it."), assistant("Done."), user("Next."), system("Update.")])
expect(yield* lower([Message.user("Before."), Message.system("One."), Message.system("Two.")])).toEqual([
user("Before."),
system("One."),
system("Two."),
])
expect(
yield* lower([
Message.user("Fix it."),
Message.assistant("Done."),
Message.system("One."),
Message.user("Next."),
Message.system("Two."),
Message.assistant("After."),
]),
).toEqual([
user("Fix it."),
assistant("Done."),
user("Next."),
system("One."),
system("Two."),
assistant("After."),
])
expect(
yield* lower([
Message.user("Use the tool."),
Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]),
Message.tool({ id: "call_1", name: "lookup", result: "Done." }),
Message.system("Update."),
Message.user("Also check tests."),
]),
).toEqual([
user("Use the tool."),
{ role: "assistant", content: [{ type: "tool_use", id: "call_1", name: "lookup", input: {} }] },
{ role: "user", content: [{ type: "tool_result", tool_use_id: "call_1", content: '"Done."' }] },
user("Also check tests."),
system("Update."),
])
}),
)
it.effect("keeps wrapped system updates in place for models without native system updates", () =>
it.effect("keeps a terminal Vertex system update in the tool-result turn", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model,
model: vertexOpus48,
messages: [
Message.user("Fix it."),
Message.assistant("Done."),
Message.system("Update."),
Message.user("Next."),
Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]),
Message.tool({ id: "call_1", name: "lookup", result: "Done." }),
Message.system("Operator update."),
],
cache: "none",
}),
)
expect(prepared.body.messages).toEqual([
{ role: "user", content: [{ type: "text", text: "Fix it." }] },
{ role: "assistant", content: [{ type: "text", text: "Done." }] },
{ role: "user", content: [{ type: "text", text: "<system-update>\nUpdate.\n</system-update>" }] },
{ role: "user", content: [{ type: "text", text: "Next." }] },
])
}),
)
it.effect("sends Vertex system updates after local tool results as native system messages", () =>
Effect.gen(function* () {
const toolTurn = [
Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]),
Message.tool({ id: "call_1", name: "lookup", result: "Done." }),
Message.system("Operator update."),
]
const lowered = [
{ role: "assistant", content: [{ type: "tool_use", id: "call_1", name: "lookup", input: {} }] },
{
role: "assistant",
content: [{ type: "tool_use", id: "call_1", name: "lookup", input: {} }],
},
{
role: "user",
content: [
@@ -550,23 +477,55 @@ describe("Anthropic Messages route", () => {
is_error: undefined,
cache_control: undefined,
},
{
type: "text",
text: "<system-update>\nOperator update.\n</system-update>",
cache_control: undefined,
},
],
},
{ role: "system", content: [{ type: "text", text: "Operator update.", cache_control: undefined }] },
]
])
}),
)
const terminal = yield* compileRequest(LLM.request({ model: vertexOpus48, messages: toolTurn, cache: "none" }))
const history = yield* compileRequest(
it.effect("preserves folded tool-result system updates across multi-turn Vertex history", () =>
Effect.gen(function* () {
const prepared = yield* compileRequest(
LLM.request({
model: vertexOpus48,
messages: [...toolTurn, Message.assistant("Acknowledged."), Message.user("Next step.")],
messages: [
Message.assistant([ToolCallPart.make({ id: "call_1", name: "lookup", input: {} })]),
Message.tool({ id: "call_1", name: "lookup", result: "Done." }),
Message.system("Operator update."),
Message.assistant("Acknowledged."),
Message.user("Next step."),
],
cache: "none",
}),
)
expect(terminal.body.messages).toEqual(lowered)
expect(history.body.messages).toEqual([
...lowered,
expect(prepared.body.messages).toEqual([
{
role: "assistant",
content: [{ type: "tool_use", id: "call_1", name: "lookup", input: {} }],
},
{
role: "user",
content: [
{
type: "tool_result",
tool_use_id: "call_1",
content: '"Done."',
is_error: undefined,
cache_control: undefined,
},
{
type: "text",
text: "<system-update>\nOperator update.\n</system-update>",
cache_control: undefined,
},
],
},
{ role: "assistant", content: [{ type: "text", text: "Acknowledged." }] },
{ role: "user", content: [{ type: "text", text: "Next step." }] },
])
@@ -1,233 +0,0 @@
import { expect } from "bun:test"
import { Effect, Schema } from "effect"
import { LLM, LLMEvent, LLMRequest, Message, ToolChoice, ToolDefinition } from "../../src/index.js"
import { Venice } from "../../src/providers.js"
import { LLMClient } from "../../src/route.js"
import { compileRequest } from "../../src/route/client.js"
import { recordedTests } from "../recorded-test.js"
const apiKey = process.env.VENICE_API_KEY ?? "fixture"
const recorded = recordedTests({
prefix: "venice-chat",
provider: "venice",
protocol: "venice-chat",
requires: ["VENICE_API_KEY"],
})
const lookup = ToolDefinition.make({
name: "lookup_rate",
description: "Retrieve the private conversion rate for a code. Only this tool knows the rate.",
inputSchema: {
type: "object",
properties: { code: { type: "string", enum: ["ZEBRA", "MOOSE"] } },
required: ["code"],
additionalProperties: false,
},
})
for (const item of [
{ model: "deepseek-v4-flash", reasoning: "scalar" },
{ model: "claude-opus-4-8", reasoning: "signed", effort: "high" },
{ model: "gemini-3-1-pro-preview", reasoning: "details", effort: "high" },
{ model: "openai-gpt-54-mini", reasoning: "encrypted" },
] as const) {
recorded.effect.with(
`${item.model} preserves reasoning through a tool loop and follow-up`,
{
tags: ["reasoning", "tool", "tool-loop", "continuation", "usage", item.reasoning],
metadata: { model: item.model },
},
() =>
Effect.gen(function* () {
const request = LLM.request({
model: Venice.configure({ apiKey }).chat(item.model),
system:
"Use the provided tool for private data. After receiving the result, give only the requested number; do not call the tool again.",
prompt:
"Look up conversion rate for code ZEBRA. Then multiply that rate by 17 and add 9. You must use lookup_rate before answering.",
tools: [lookup],
generation: { maxTokens: 4096 },
providerOptions: "effort" in item ? { reasoningEffort: item.effort } : undefined,
})
const first = yield* LLMClient.generate(request)
expect(first.toolCalls).toMatchObject([{ name: "lookup_rate", input: { code: "ZEBRA" } }])
expect(first.finishReason.normalized).toBe("tool-calls")
expect(first.events.filter(LLMEvent.is.finish)).toHaveLength(1)
const continuation = LLMRequest.update(request, {
toolChoice: ToolChoice.make("none"),
messages: [
...request.messages,
first.message,
...first.toolCalls.map((call) =>
Message.tool({ id: call.id, name: call.name, result: { code: "ZEBRA", rate: 23 } }),
),
],
})
const compiled = yield* compileRequest(continuation)
const assistant = compiled.body.messages.find((message) => message.role === "assistant")
expect(assistant).toBeDefined()
// Some models call a tool immediately and only reason after its result.
if (item.reasoning === "scalar" && first.reasoning.length > 0) {
expect(assistant).toMatchObject({ reasoning_content: first.reasoning })
}
if (item.reasoning === "signed") {
const details = first.message.content
.filter((part) => part.type === "reasoning")
.flatMap((part) => part.providerMetadata?.venice?.reasoningDetails ?? [])
expect(details).toEqual(
expect.arrayContaining([
expect.objectContaining({ type: "reasoning.text", signature: expect.any(String) }),
]),
)
expect(assistant).toMatchObject({ reasoning_details: details })
}
if (item.reasoning === "details")
expect(assistant).toMatchObject({
reasoning_details: expect.arrayContaining([
expect.objectContaining({ type: "reasoning.encrypted", data: expect.any(String) }),
]),
})
if (item.reasoning === "encrypted") {
expect(first.reasoning).not.toContain("__ENCRYPTED_REASONING__")
const details = first.message.content
.filter((part) => part.type === "reasoning")
.flatMap((part) => part.providerMetadata?.venice?.reasoningDetails ?? [])
if (details.length > 0) expect(assistant).toMatchObject({ reasoning_details: details })
}
const second = yield* LLMClient.generate(continuation)
expect(second.text).toContain("400")
expect(second.reasoning).not.toContain("__ENCRYPTED_REASONING__")
expect(second.toolCalls).toHaveLength(0)
expect(second.finishReason.normalized).toBe("stop")
expect(second.usage?.inputTokens).toBeGreaterThan(0)
expect(second.usage?.outputTokens).toBeGreaterThan(0)
const followUp = LLMRequest.update(continuation, {
messages: [
...continuation.messages,
second.message,
Message.user("Add 7 to your previous final answer. Reply with just the number."),
],
})
const third = yield* LLMClient.generate(followUp)
expect(third.text).toContain("407")
expect(third.finishReason.normalized).toBe("stop")
if (item.reasoning === "encrypted") {
expect(
[first, second, third].flatMap((response) =>
response.message.content
.filter((part) => part.type === "reasoning")
.flatMap((part) => part.providerMetadata?.venice?.reasoningDetails ?? []),
),
).toEqual(
expect.arrayContaining([
expect.objectContaining({ type: "reasoning.encrypted", data: expect.any(String) }),
]),
)
}
}),
180_000,
)
}
recorded.effect.with(
"Gemini preserves per-call signatures through parallel tools",
{ tags: ["tool", "parallel", "signature"] },
() =>
Effect.gen(function* () {
const request = LLM.request({
model: Venice.configure({ apiKey }).chat("gemini-3-8-flash"),
prompt:
"Look up private rates for both ZEBRA and MOOSE using two calls to lookup_rate. Then add the rates and multiply by 7. Use parallel calls if possible.",
tools: [lookup],
providerOptions: { reasoningEffort: "high", parallelToolCalls: true },
generation: { maxTokens: 4096 },
})
const first = yield* LLMClient.generate(request)
expect(first.toolCalls).toHaveLength(2)
const continuation = LLMRequest.update(request, {
toolChoice: ToolChoice.make("none"),
messages: [
...request.messages,
first.message,
...first.toolCalls.map((call) =>
Message.tool({
id: call.id,
name: call.name,
result: {
rate:
Schema.decodeUnknownSync(Schema.Struct({ code: Schema.String }))(call.input).code === "ZEBRA"
? 23
: 31,
},
}),
),
],
})
const compiled = yield* compileRequest(continuation)
expect(compiled.body.messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
tool_calls: first.toolCalls.map((call) =>
expect.objectContaining({
id: call.id,
...(call.providerMetadata?.venice?.thoughtSignature === undefined
? {}
: { thought_signature: call.providerMetadata.venice.thoughtSignature }),
}),
),
}),
]),
)
const second = yield* LLMClient.generate(continuation)
expect(second.text).toContain("378")
const signature = second.message.content
.filter((part) => part.type === "text")
.map((part) => part.providerMetadata?.venice?.messageThoughtSignature)
.find((value) => value !== undefined)
expect(signature).toEqual(expect.any(String))
const followUp = LLMRequest.update(continuation, {
messages: [
...continuation.messages,
second.message,
Message.user("Add 7 to your previous final answer. Reply with just the number."),
],
})
expect((yield* compileRequest(followUp)).body.messages).toEqual(
expect.arrayContaining([expect.objectContaining({ role: "assistant", thought_signature: signature })]),
)
expect((yield* LLMClient.generate(followUp)).text).toContain("385")
}),
180_000,
)
recorded.effect.with(
"Qwen disables reasoning",
{ tags: ["text", "reasoning", "toggle"] },
() =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(
LLM.request({
model: Venice.configure({ apiKey }).chat("qwen3-6-27b"),
prompt: "What is 23 multiplied by 17 plus 9? Reply with just the number.",
providerOptions: { reasoning: { enabled: false } },
generation: { maxTokens: 4096 },
}),
)
expect(response.text).toContain("400")
expect(response.reasoning).toBe("")
}),
120_000,
)
recorded.effect.with("surfaces Venice model errors", { tags: ["error"] }, () =>
Effect.gen(function* () {
const result = yield* LLMClient.generate(
LLM.request({ model: Venice.configure({ apiKey }).chat("no-such-model-xyz"), prompt: "Hello" }),
).pipe(Effect.result)
expect(result._tag).toBe("Failure")
if (result._tag === "Failure") {
expect(result.failure.reason._tag).toBe("InvalidRequest")
expect(result.failure.message).toContain("Specified model not found")
expect(result.failure.reason.http?.status).toBe(404)
}
}),
)
-370
View File
@@ -1,370 +0,0 @@
import { expect } from "bun:test"
import { ConfigProvider, Effect } from "effect"
import { Headers } from "effect/unstable/http"
import { Auth, LLM, LLMClient, Message } from "../../src/index.js"
import { OpenAI, Venice } from "../../src/providers.js"
import { compileRequest } from "../../src/route/client.js"
import { Endpoint } from "../../src/route/endpoint.js"
import { it } from "../lib/effect.js"
import { fixedResponse } from "../lib/http.js"
import { sseEvents } from "../lib/sse.js"
const model = Venice.configure({ apiKey: "fixture" }).chat("fixture-model")
const frame = (delta: Record<string, unknown>, finish_reason: string | null = null) => ({
choices: [{ delta, finish_reason }],
})
it.effect("Venice owns its endpoint, environment auth, aliases, and SDK-compatible prompt default", () =>
Effect.gen(function* () {
expect(Venice.provider.model).toBe(Venice.provider.chat)
const selected = Venice.chat("qwen3-6-27b")
const request = LLM.request({ model: selected, prompt: "Hello", promptCacheKey: "session-123" })
const compiled = yield* compileRequest(request)
const url = Endpoint.render(selected.route.endpoint, { request, body: compiled.body }).toString()
expect(url).toBe("https://api.venice.ai/api/v1/chat/completions")
expect(selected.provider).toBe("venice")
expect(compiled.body.venice_parameters).toEqual({ include_venice_system_prompt: false })
expect(compiled.body.prompt_cache_key).toBe("session-123")
expect(compiled.body.store).toBeUndefined()
expect(compiled.body.stream_options).toEqual({ include_usage: true })
const headers = yield* selected.route.auth.apply({
request,
method: "POST",
url,
body: "{}",
headers: Headers.empty,
})
expect(headers.authorization).toBe("Bearer fixture")
const custom = Venice.configure({
baseURL: "https://gateway.example/v1",
queryParams: { deployment: "private" },
auth: Auth.header("x-key", "fixture"),
}).chat("qwen3-6-27b")
expect(Endpoint.render(custom.route.endpoint, { request, body: compiled.body }).toString()).toBe(
"https://gateway.example/v1/chat/completions?deployment=private",
)
}).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: { VENICE_API_KEY: "fixture" } })))),
)
it.effect("Venice lowers typed options, model defaults, and explicit reasoning overrides", () =>
Effect.gen(function* () {
const compiled = yield* compileRequest(
LLM.request({
model: Venice.configure({ apiKey: "fixture", providerOptions: { reasoningEffort: "low" } }).model(
"qwen3-6-27b",
),
generation: { maxTokens: 200, topK: 20 },
providerOptions: {
reasoningEffort: "future-effort",
reasoning: { effort: "high", enabled: true, summary: "concise" },
veniceParameters: { includeVeniceSystemPrompt: true, enableWebSearch: "auto", stripThinkingResponse: false },
parallelToolCalls: false,
maxCompletionTokens: 123,
minP: 0.1,
repetitionPenalty: 1.1,
stopTokenIds: [12],
promptCacheRetention: "24h",
logprobs: true,
topLogprobs: 2,
minTemp: 0.1,
maxTemp: 1.5,
responseFormat: { type: "json_object" },
user: "user-123",
},
}),
)
expect(compiled.body).toMatchObject({
max_completion_tokens: 123,
top_k: 20,
min_p: 0.1,
repetition_penalty: 1.1,
reasoning: { effort: "future-effort", enabled: true, summary: "concise" },
venice_parameters: {
include_venice_system_prompt: true,
enable_web_search: "auto",
strip_thinking_response: false,
},
parallel_tool_calls: false,
stop_token_ids: [12],
prompt_cache_retention: "24h",
logprobs: true,
top_logprobs: 2,
min_temp: 0.1,
max_temp: 1.5,
response_format: { type: "json_object" },
user: "user-123",
})
expect(compiled.body.reasoning_effort).toBeUndefined()
expect(compiled.body.max_tokens).toBeUndefined()
expect(
(yield* compileRequest(LLM.request({ model, providerOptions: { reasoning: { enabled: false } } }))).body
.reasoning,
).toEqual({ enabled: false })
expect(
(yield* compileRequest(LLM.request({ model, cache: "none", promptCacheKey: "session-123" }))).body
.prompt_cache_key,
).toBeUndefined()
}),
)
it.effect("Venice hides encrypted trailers across every marker split while preserving exact replay", () =>
Effect.gen(function* () {
const marker = "__ENCRYPTED_REASONING__"
for (const split of Array.from({ length: marker.length + 1 }, (_, index) => index)) {
const raw = `Visible summary\n${marker}id=rs_123\nopaque-payload`
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(
fixedResponse(
sseEvents(
frame({ reasoning_content: `Visible summary\n${marker.slice(0, split)}` }),
frame({
reasoning_content: `${marker.slice(split)}id=rs_123\nopaque-payload`,
reasoning_details: [
{
type: "reasoning.encrypted",
data: "opaque-payload",
format: "openai-responses-v1",
id: "rs_123",
index: 0,
},
],
}),
frame({ content: "42" }, "stop"),
),
),
),
)
expect(response.reasoning).toBe("Visible summary\n")
expect(
response.events
.filter((event) => event.type === "reasoning-delta")
.map((event) => event.text)
.join(""),
).toBe("Visible summary\n")
const replay = yield* compileRequest(LLM.request({ model, messages: [Message.user("Hello"), response.message] }))
expect(replay.body.messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
role: "assistant",
reasoning_content: raw,
reasoning_details: [
{
type: "reasoning.encrypted",
data: "opaque-payload",
format: "openai-responses-v1",
id: "rs_123",
index: 0,
},
],
}),
]),
)
}
}),
)
it.effect("Venice preserves opaque-only reasoning and does not eat an incomplete marker prefix", () =>
Effect.gen(function* () {
for (const scalar of ["__ENCRYPTED_REASONING__opaque", "normal _", "normal __ENCRYPTED_"]) {
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(
fixedResponse(sseEvents(frame({ reasoning_content: scalar }), frame({ content: "42" }, "stop"))),
),
)
expect(response.reasoning).toBe(scalar.startsWith("__ENCRYPTED_REASONING__") ? "" : scalar)
const body = (yield* compileRequest(LLM.request({ model, messages: [response.message] }))).body
expect(body.messages).toEqual(expect.arrayContaining([expect.objectContaining({ reasoning_content: scalar })]))
}
}),
)
it.effect("Venice encrypted trailers do not change the base OpenAI Chat protocol", () =>
LLMClient.generate(
LLM.request({ model: OpenAI.configure({ apiKey: "fixture" }).chat("gpt-4o"), prompt: "Hello" }),
).pipe(
Effect.provide(
fixedResponse(
sseEvents(
frame({ reasoning_content: "text __ENCRYPTED_REASONING__payload" }),
frame({ content: "42" }, "stop"),
),
),
),
Effect.tap((response) => Effect.sync(() => expect(response.reasoning).toBe("text __ENCRYPTED_REASONING__payload"))),
),
)
it.effect("Venice keeps late Gemini signatures attached to the correct parallel call", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Lookup" })).pipe(
Effect.provide(
fixedResponse(
sseEvents(
frame({
tool_calls: [
{ index: 0, id: "call_0", function: { name: "lookup", arguments: '{"key":"A"}' } },
{ index: 1, id: "call_1", function: { name: "lookup", arguments: '{"key":"B"}' } },
],
}),
frame({ tool_calls: [{ index: 0, thought_signature: "signed-first-call" }] }),
frame({}, "tool_calls"),
),
),
),
)
expect(response.toolCalls).toHaveLength(2)
expect(response.toolCalls[0].providerMetadata?.venice?.thoughtSignature).toBe("signed-first-call")
expect(response.toolCalls[1].providerMetadata?.venice?.thoughtSignature).toBeUndefined()
const replay = yield* compileRequest(LLM.request({ model, messages: [Message.user("Lookup"), response.message] }))
expect(replay.body.messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
tool_calls: [
expect.objectContaining({ id: "call_0", thought_signature: "signed-first-call" }),
expect.not.objectContaining({ thought_signature: expect.anything() }),
],
}),
]),
)
}),
)
it.effect("Venice preserves message signatures even for identical answers and empty intervening messages", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(
fixedResponse(
sseEvents(frame({ content: "42" }), frame({ thought_signature: "second-signature" }), frame({}, "stop")),
),
),
)
const request = LLM.request({
model,
messages: [
Message.assistant([
{ type: "text", text: "42", providerMetadata: { venice: { messageThoughtSignature: "first-signature" } } },
]),
Message.assistant(""),
response.message,
],
})
expect((yield* compileRequest(request)).body.messages).toEqual([
{ role: "assistant", content: "42", thought_signature: "first-signature" },
{ role: "assistant", content: "42", thought_signature: "second-signature" },
])
}),
)
it.effect("Venice merges signed Claude reasoning without losing its final signature", () =>
Effect.gen(function* () {
const response = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(
fixedResponse(
sseEvents(
frame({
reasoning_content: "Think ",
reasoning_details: [{ type: "reasoning.text", text: "Think ", format: "anthropic-claude-v1", index: 0 }],
}),
frame({
reasoning_content: "carefully",
reasoning_details: [
{ type: "reasoning.text", text: "carefully", format: "anthropic-claude-v1", index: 0 },
],
}),
frame({
reasoning_details: [
{
type: "reasoning.text",
text: "",
signature: "claude-signature",
format: "anthropic-claude-v1",
index: 0,
},
],
}),
frame({ content: "42" }, "stop"),
),
),
),
)
expect(response.reasoning).toBe("Think carefully")
expect((yield* compileRequest(LLM.request({ model, messages: [response.message] }))).body.messages).toEqual(
expect.arrayContaining([
expect.objectContaining({
reasoning_details: [
{
type: "reasoning.text",
text: "Think carefully",
signature: "claude-signature",
format: "anthropic-claude-v1",
index: 0,
},
],
}),
]),
)
}),
)
it.effect("Venice maps cache-write usage and retains the raw wire spelling", () =>
LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(
fixedResponse(
sseEvents(frame({ content: "42" }, "stop"), {
choices: [],
usage: {
prompt_tokens: 100,
completion_tokens: 20,
total_tokens: 120,
prompt_tokens_details: { cached_tokens: 40, cache_creation_input_tokens: 30 },
completion_tokens_details: { reasoning_tokens: 10 },
},
}),
),
),
Effect.tap((response) =>
Effect.sync(() => {
expect(response.usage).toMatchObject({
inputTokens: 100,
outputTokens: 20,
nonCachedInputTokens: 30,
cacheReadInputTokens: 40,
cacheWriteInputTokens: 30,
reasoningTokens: 10,
})
expect(response.usage?.providerMetadata?.venice).toMatchObject({
prompt_tokens_details: { cache_creation_input_tokens: 30 },
})
}),
),
),
)
it.effect("Venice surfaces HTTP string errors and streaming validation details without losing the payload", () =>
Effect.gen(function* () {
const http = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(
fixedResponse('{"error":"Specified model not found"}', {
status: 404,
headers: { "content-type": "application/json" },
}),
),
Effect.result,
)
expect(http._tag).toBe("Failure")
if (http._tag === "Failure") expect(http.failure.message).toContain("Specified model not found")
const payload = {
error: "Invalid request parameters",
issues: [{ message: "Unsupported value", path: ["reasoning", "effort"] }],
}
const streamed = yield* LLMClient.generate(LLM.request({ model, prompt: "Hello" })).pipe(
Effect.provide(fixedResponse(sseEvents(payload))),
Effect.result,
)
expect(streamed._tag).toBe("Failure")
if (streamed._tag === "Failure") {
expect(streamed.failure.message).toContain("reasoning.effort: Unsupported value")
expect(JSON.parse(streamed.failure.reason.body ?? "{}")).toEqual(payload)
}
}),
)
+1
View File
@@ -134,6 +134,7 @@
"mime-types": "3.0.2",
"tree-sitter-bash": "0.25.0",
"tree-sitter-powershell": "0.25.10",
"venice-ai-sdk-provider": "2.1.1",
"web-tree-sitter": "0.25.10",
"which": "6.0.1",
"zod": "catalog:"
-1
View File
@@ -70,7 +70,6 @@ const PACKAGES: Readonly<Record<string, string>> = {
"@ai-sdk/xai": "@opencode/ai/providers/xai",
"@openrouter/ai-sdk-provider": "@opencode/ai/providers/openrouter",
"ai-gateway-provider": "@opencode/ai/providers/cloudflare-ai-gateway",
"venice-ai-sdk-provider": "@opencode/ai/providers/venice",
}
const protocols = (name: string) => ({
+10 -5
View File
@@ -7,13 +7,19 @@ import { Permission } from "../permission.js"
const PROMPT_EXPLORE = `You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
Your strengths:
- Rapidly finding files using glob patterns
- Searching code and text with powerful regex patterns
- Reading and analyzing file contents
Guidelines:
- Your role is EXCLUSIVELY to search and analyze
- Parallelize independent tool calls for searches and reads whenever possible
- Use Glob for broad file pattern matching
- Use Grep for searching file contents with regex
- Use Read when you know the specific file path you need to read
- Adapt your search approach based on the thoroughness level specified by the caller
- Return file paths as absolute paths in your final response
- You MUST NOT create, modify, delete, move, or copy files, including temporary files and reports
- Shell commands MUST be read-only. NEVER run commands that write files or change system state
- For clear communication, avoid using emojis
- Do not create any files, or run bash commands that modify the user's system state in any way
Complete the user's search request efficiently and report your findings clearly.`
@@ -109,7 +115,6 @@ export const Plugin = define({
...Permission.merge(
[
{ action: "*", resource: "*", effect: "deny" },
{ action: "shell", resource: "*", effect: "allow" },
{ action: "grep", resource: "*", effect: "allow" },
{ action: "glob", resource: "*", effect: "allow" },
{ action: "webfetch", resource: "*", effect: "allow" },
+2
View File
@@ -26,6 +26,7 @@ import { PoePlugin } from "./provider/poe.js"
import { PromptCacheKeyPlugin } from "./provider/prompt-cache-key.js"
import { SapAICorePlugin } from "./provider/sap-ai-core.js"
import { VercelPlugin } from "./provider/vercel.js"
import { VenicePlugin } from "./provider/venice.js"
import { VLLMPlugin } from "./provider/vllm.js"
import { XAIPlugin } from "./provider/xai.js"
import { ZenmuxPlugin } from "./provider/zenmux.js"
@@ -59,6 +60,7 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [
PromptCacheKeyPlugin,
SapAICorePlugin,
VercelPlugin,
VenicePlugin,
VLLMPlugin,
XAIPlugin,
ZenmuxPlugin,
@@ -0,0 +1,10 @@
import { createProviderPlugin } from "./factory.js"
export const VenicePlugin = createProviderPlugin({
id: "opencode.provider.venice",
package: "venice-ai-sdk-provider",
load: async (options) => {
const { createVenice } = await import("venice-ai-sdk-provider")
return createVenice(options)
},
})
-1
View File
@@ -97,7 +97,6 @@ const builtins = new Map<string, () => Promise<unknown>>([
["@opencode/ai/providers/openai-compatible", () => import("@opencode/ai/providers/openai-compatible")],
["@opencode/ai/providers/openrouter", () => import("@opencode/ai/providers/openrouter")],
["@opencode/ai/providers/togetherai", () => import("@opencode/ai/providers/togetherai")],
["@opencode/ai/providers/venice", () => import("@opencode/ai/providers/venice")],
["@opencode/ai/providers/xai", () => import("@opencode/ai/providers/xai")],
["@opencode/ai/providers/zai/chat", () => import("@opencode/ai/providers/zai/chat")],
["@opencode/ai/providers/zai-coding-plan/chat", () => import("@opencode/ai/providers/zai-coding-plan/chat")],
+10 -25
View File
@@ -19,11 +19,9 @@ import type { SessionCompactionResult } from "@opencode/plugin/effect/session"
import type { SessionError } from "@opencode/schema/session-error"
import { makeLocationNode } from "@opencode/util/effect/app-node"
import { Context, Effect, Layer, Result, Stream } from "effect"
import { Agent } from "../agent.js"
import { Bus } from "../bus.js"
import { Database } from "../database/database.js"
import { llmClient } from "../effect/app-node-platform.js"
import { Model } from "../model.js"
import { State } from "../state.js"
import { Token } from "../util/token.js"
import type { SessionContext } from "./context.js"
@@ -32,7 +30,6 @@ import { SessionHistory } from "./history.js"
import type { SessionMessage } from "./message.js"
import { SessionModelRequest } from "./model-request.js"
import { SessionProviderContext } from "./provider-context.js"
import { SessionRunnerModel } from "./runner/model.js"
import { SessionRunnerRetry } from "./runner/retry.js"
import { toLLMMessages } from "./runner/to-llm-message.js"
import { contentFilterError, toSessionError } from "./to-session-error.js"
@@ -184,11 +181,8 @@ const LEGACY_HEADING = "## Additional Context"
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const agents = yield* Agent.Service
const bus = yield* Bus.Service
const llm = yield* LLMClient.Service
const catalog = yield* Model.Service
const models = yield* SessionRunnerModel.Service
const db = (yield* Database.Service).db
const requests = yield* SessionModelRequest.Service
@@ -208,33 +202,24 @@ export const layer = Layer.effect(
// Only the user compacts when automatic compaction is off, overflow included.
if (trigger.reason !== "manual" && !settings.auto) return { status: "skipped" }
if (trigger.reason === "auto" && !due(context, calculateCeiling(context.model.limit, settings.buffer)))
return { status: "skipped" }
const native = context.model.compaction?.type === "native"
const agent = native ? undefined : yield* agents.get(Agent.ID.make("compaction"))
const model =
(agent?.model &&
(yield* models
.resolve({ ...context.session, model: agent.model }, catalog.available)
.pipe(Effect.orElseSucceed(() => undefined)))) ??
context.model
const ceiling = calculateCeiling(model.limit, settings.buffer)
const ceiling = calculateCeiling(context.model.limit, settings.buffer)
if (trigger.reason === "auto" && !due(context, ceiling)) return { status: "skipped" }
// An unknown window never triggers auto compaction, but the compaction request still needs a size to aim for.
const cap = Number.isFinite(ceiling)
? ceiling
: calculateCeiling({ ...model.limit, context: UNKNOWN_WINDOW }, settings.buffer)
: calculateCeiling({ ...context.model.limit, context: UNKNOWN_WINDOW }, settings.buffer)
// The provider just rejected this context, so the estimate ran low; the first attempt already aims below it.
const budget =
trigger.reason === "overflow" ? Math.min(cap, Math.floor(estimateContext(context) * SHRINK_STEPS[0])) : cap
const selected = model === context.model ? trigger : { ...trigger, context: { ...context, model } }
const compaction = native
? compactNatively(selected, budget, settings.keep)
: summarize(selected, budget, settings.keep)
const compaction =
context.model.compaction?.type === "native"
? compactNatively(trigger, budget, settings.keep)
: summarize(trigger, budget, settings.keep)
return yield* compaction.pipe(
Effect.matchEffect({
onSuccess: (result) => publish(selected, result),
onFailure: (failure) => publish(selected, failure),
onSuccess: (result) => publish(trigger, result),
onFailure: (failure) => publish(trigger, failure),
}),
)
})
@@ -677,7 +662,7 @@ export const layer = Layer.effect(
export const node = makeLocationNode({
service: Service,
layer,
deps: [Agent.node, Bus.node, Database.node, llmClient, Model.node, SessionModelRequest.node, SessionRunnerModel.node],
deps: [Bus.node, Database.node, llmClient, SessionModelRequest.node],
})
/** History loads from the latest completed compaction, so a previous one is always the first message. */
+19 -10
View File
@@ -210,7 +210,10 @@ export const scanPortable = Effect.fnUntraced(function* (command: string, shell:
catch: (cause) => new Error(`Portable shell scanner failed to load: ${cause}`, { cause }),
})
const powershell = ShellSelect.ps(shell)
const result = powershell ? ShellScan.scanPowerShell(command) : ShellScan.scan(command)
const name = ShellSelect.name(shell)
const result = powershell
? ShellScan.scanPowerShell(command)
: ShellScan.scan(command, name === "bash" || name === "zsh" ? name : "posix")
if (result.kind === "opaque")
return yield* Effect.fail(new Error(`Portable shell scanner cannot analyze command: ${result.reason}`))
@@ -225,15 +228,21 @@ export const scanPortable = Effect.fnUntraced(function* (command: string, shell:
if (CWD.has(name)) {
output.directories.push(
...directoryArgs(
words.flatMap((text): Part[] => {
const parameter = powershell ? /^(-(?:literalpath|path)):(.*)$/i.exec(text) : undefined
if (parameter)
return [
{ type: "command_parameter", text: parameter[1] },
{ type: "word", text: parameter[2] },
]
return [{ type: powershell && text.startsWith("-") ? "command_parameter" : "word", text }]
}),
powershell
? words.flatMap((text): Part[] => {
const parameter = /^(-(?:literalpath|path)):(.*)$/i.exec(text)
if (parameter)
return [
{ type: "command_parameter", text: parameter[1] },
{ type: "word", text: parameter[2] },
]
return [{ type: text.startsWith("-") ? "command_parameter" : "word", text }]
})
: item.rawWords.map((text, index) => ({
type: "word",
// Only literal quoting resolves to a static directory.
text: text.startsWith("$'") || (!/[$`~\\]/.test(text) && /['"]/.test(text)) ? item.words[index] : text,
})),
powershell,
cwd,
shell,
File diff suppressed because it is too large. Load diff
+1 -10
View File
@@ -220,14 +220,6 @@ const openaiCompatible: Protocol = (model, support) => {
return openaiChat(model, support)
}
const veniceChat: Protocol = (_, support) => {
if (support.type === "effort")
return efforts(support.values ?? EFFORTS, (effort) => ({ settings: { reasoningEffort: effort } }))
if (support.type === "toggle")
return toggle({ settings: { reasoning: { enabled: false } } }, { settings: { reasoning: { enabled: true } } })
return []
}
const moonshotChat: Protocol = (model, support) => {
const id = modelID(model).toLowerCase()
const toggleable = id.includes("k2.5") || id.includes("k2-5") || id.includes("k2.6") || id.includes("k2-6")
@@ -591,7 +583,6 @@ const PROTOCOLS: Readonly<Record<string, Protocol>> = {
"@opencode/ai/providers/mistral": openaiChat,
"@opencode/ai/providers/moonshot/chat": moonshotChat,
"@opencode/ai/providers/togetherai": openaiChat,
"@opencode/ai/providers/venice": veniceChat,
"@opencode/ai/providers/xai": xaiResponses,
"@opencode/ai/providers/zai/chat": zaiChat,
"@opencode/ai/providers/zai-coding-plan/chat": zaiChat,
@@ -611,7 +602,7 @@ const PROTOCOLS: Readonly<Record<string, Protocol>> = {
"@opencode/ai/providers/cohere": cohere,
"@opencode/ai/providers/openrouter": openrouter,
[Provider.aisdk("venice-ai-sdk-provider")]: veniceChat,
[Provider.aisdk("venice-ai-sdk-provider")]: openaiChat,
"@opencode/ai/providers/cloudflare-ai-gateway": cloudflareAIGateway,
[Provider.aisdk("@ai-sdk/gateway")]: vercelGateway,
[Provider.aisdk("@jerome-benoit/sap-ai-provider-v2")]: sapAICore,
-2
View File
@@ -203,8 +203,6 @@ describe("Agent", () => {
expect(Permission.evaluate("external_directory", path.join(global.config, "*"), permissions).effect).toBe("allow")
expect(Permission.evaluate("external_directory", path.join(global.tmp, "*"), permissions).effect).toBe("allow")
const explore = yield* agent.get(Agent.ID.make("explore"))
expect(Permission.evaluate("shell", "git log -5", explore?.permissions ?? []).effect).toBe("allow")
expect(Permission.evaluate("edit", "src/index.ts", explore?.permissions ?? []).effect).toBe("deny")
expect(Permission.evaluate("read", ".env", explore?.permissions ?? []).effect).toBe("ask")
expect(Permission.evaluate("read", ".env.local", explore?.permissions ?? []).effect).toBe("ask")
expect(Permission.evaluate("read", ".env.example", explore?.permissions ?? []).effect).toBe("allow")
@@ -46,7 +46,6 @@ const it = testEffect(
}),
),
Config.node.replace(config),
Location.node.replace(Location.boundNode({ directory: AbsolutePath.make("/tmp") })),
]),
),
)
@@ -7,6 +7,7 @@ import { PluginHost } from "@opencode/core/plugin/host"
import { CoherePlugin } from "@opencode/core/plugin/provider/cohere"
import { GatewayPlugin } from "@opencode/core/plugin/provider/gateway"
import { PerplexityPlugin } from "@opencode/core/plugin/provider/perplexity"
import { VenicePlugin } from "@opencode/core/plugin/provider/venice"
import { Provider } from "@opencode/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@@ -17,6 +18,7 @@ const providers = [
{ id: "cohere", plugin: CoherePlugin, package: "@ai-sdk/cohere", provider: "cohere.chat" },
{ id: "gateway", plugin: GatewayPlugin, package: "@ai-sdk/gateway", provider: "gateway" },
{ id: "perplexity", plugin: PerplexityPlugin, package: "@ai-sdk/perplexity", provider: "perplexity" },
{ id: "venice", plugin: VenicePlugin, package: "venice-ai-sdk-provider", provider: "custom-provider.chat" },
] as const
const it = testEffect(PluginTestLayer)
@@ -18,7 +18,6 @@ import { SessionRunnerModel } from "@opencode/core/session/runner/model"
import { SessionTable } from "@opencode/core/session/sql"
import { SessionStore } from "@opencode/core/session/store"
import { Session } from "@opencode/core/session"
import { Location } from "@opencode/core/location"
import { Project } from "@opencode/core/project"
import { ProjectTable } from "@opencode/core/project/sql"
import { App } from "@opencode/core/app"
@@ -88,11 +87,7 @@ const it = testEffect(
SessionModelRequest.node,
PluginHooks.node,
]),
[
Bus.node.replace(Bus.configured({ persist: true })),
llmClient.replace(client),
Location.node.replace(Location.boundNode({ directory: AbsolutePath.make("/project") })),
],
[Bus.node.replace(Bus.configured({ persist: true })), llmClient.replace(client)],
),
)
@@ -8,7 +8,6 @@ import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
import { llmClient } from "@opencode/core/effect/app-node-platform"
import { Instructions } from "@opencode/core/instructions/index"
import { PluginHooks } from "@opencode/core/plugin/hooks"
import { Location } from "@opencode/core/location"
import { Project } from "@opencode/core/project"
import { ProjectTable } from "@opencode/core/project/sql"
import { AbsolutePath } from "@opencode/core/schema"
@@ -41,10 +40,7 @@ const it = testEffect(
PluginHooks.node,
llmClient,
]),
[
Bus.node.replace(Bus.configured({ persist: true })),
Location.node.replace(Location.boundNode({ directory: AbsolutePath.make("/project") })),
],
[Bus.node.replace(Bus.configured({ persist: true }))],
),
)
@@ -42,6 +42,21 @@ describe("ShellScan adversarial corpus", () => {
expect(result.commands.map((command) => command.words[0])).toEqual([...names])
})
// Only shell sinks evaluate subscripts; ordinary arguments, heredoc bodies, and bound data without a sink are safe.
test.each([
"bun -e 'f(`a[${x}]`)'",
"rg 'a[$(x)]'",
"cat <<'EOF'\na[$(x)]\nEOF",
"echo 'a[$(x)]'",
'BODY="- [x] Fix \\`scan.ts\\`"; gh pr create --title "fix" --body "$BODY"',
"MSG='[feat] Fix `foo`'; git commit -m \"$MSG\"",
"PATTERN='a[${b}]'; rg \"$PATTERN\"",
"export REGEX='[0-9]+${foo}'",
"cat <<< 'const a = arr[0]; const b = `${a}`'",
])("scans subscript-shaped text outside shell sinks: %s", (input) => {
expect(ShellScan.scan(input).kind).toBe("scanned")
})
test.each(['printf "unterminated', "printf ok &&", "printf ok >", "echo > >out"])(
"keeps structurally uncertain Bash input opaque: %s",
(input) => {
@@ -49,6 +64,19 @@ describe("ShellScan adversarial corpus", () => {
},
)
test.each([
["repeated assignment value operators", "x[a]" + "=]".repeat(32_000)],
["unclosed assignment subscripts", "a[\n".repeat(21_000)],
["case patterns with substitutions", "case x in " + "[$(:)".repeat(10_000)],
["nested groups with bracket words", "{ ".repeat(31) + "echo " + "a[] ".repeat(15_000) + "; }".repeat(31)],
["continued heredoc lines", "cat <<E\n" + "x\\\n".repeat(20_000) + "E\n"],
["heredoc backslash runs", "cat <<E\n" + "\\".repeat(60_000) + "x\nE\n"],
])("scans adversarial Bash input in bounded time: %s", (_, input) => {
const start = performance.now()
ShellScan.scan(input)
expect(performance.now() - start).toBeLessThan(500)
})
test.each([
['pwsh --command "Remove-Item victim.txt"', ["pwsh"]],
["Import-Module ./evil.psm1", ["Import-Module"]],
@@ -60,23 +60,41 @@ const fixtures = [
["! scan_probe", ["scan_probe"]],
["time scan_probe", ["time"]],
["{fd}>/dev/null scan_probe", ["scan_probe"]],
["case $r in a) ls | head;; esac", ["ls", "head"]],
["case $r in a) ls && echo;; esac", ["ls", "echo"]],
["{ find . -exec echo {} \\; ; }", ["find"]],
["{ echo {a,{b,c}}; }", ["echo"]],
["if true; then\\\n echo hi; fi", ["true", "echo"]],
["for ((i=0; i<2; i++)) do echo hi; done", ["echo"]],
['echo "$(case x in @(a)) echo hi;; esac)"', ["echo", "echo"]],
["set -- 1; for x do scan_probe; done", ["set", "scan_probe"]],
["'q'; x=1 a", ["q", "a"]],
["cat <<E; ( true\nscan_ignored\nE\n)", ["cat", "true"]],
["cat <<E; f() { true\nscan_ignored\nE\n}; f", ["cat", "true", "f"]],
["cat <<E; case x in\nscan_ignored\nE\nx) scan_probe;; esac", ["cat", "scan_probe"]],
["time [[ ( -f foo ) ]]", []],
["time ! { echo a; echo b; }", ["echo", "echo"]],
] as const
describe("ordinary Bash and Zsh syntax", () => {
test.each(fixtures)("extracts actual command nodes: %s", (source, names) => {
const result = ShellScan.scan(source)
expect(result.kind).toBe("scanned")
if (result.kind !== "scanned") throw new Error(result.reason)
expect(result.commands.map((command) => command.words[0])).toEqual([...names])
for (const dialect of ["bash", "zsh"] as const) {
const result = ShellScan.scan(source, dialect)
expect(result.kind).toBe("scanned")
if (result.kind !== "scanned") throw new Error(result.reason)
expect(result.commands.map((command) => command.words[0])).toEqual([...names])
}
})
for (const shell of ["bash", "zsh"]) {
const executable = Bun.which(shell)
for (const [source] of fixtures) {
// These are Bash spellings; Zsh's fd allocation is a standalone statement.
// These are Bash spellings; Zsh's fd allocation is a standalone statement. Bash parses extglob
// patterns only when extglob is enabled.
test.skipIf(
!executable ||
(shell === "zsh" && (source.includes('$"') || source.startsWith("{fd}") || source.includes("$["))),
(shell === "zsh" && (source.includes('$"') || source.startsWith("{fd}") || source.includes("$["))) ||
(shell === "bash" && source.includes("@(")),
)(`${shell} accepts the source grammar: ${source}`, () => {
const result = Bun.spawnSync([
executable ?? shell,
@@ -132,10 +150,15 @@ describe("ordinary Bash and Zsh syntax", () => {
"cat <<EOF\nunclosed",
"echo ${missing",
"echo $'missing",
"case $r in a) ls |;; esac",
])("rejects incomplete syntax: %s", (source) => {
expect(ShellScan.scan(source).kind).toBe("opaque")
})
test("scans a Zsh brace group closed after a redirect", () => {
expect(ShellScan.scan("{ a && >f }")).toMatchObject({ kind: "scanned", commands: [{ words: ["a"] }] })
})
test("preserves raw lexical spelling of ANSI-C and locale quoted words", () => {
expect(ShellScan.scan("$'pri\\x6etf' $'line\\n' $\"text\"")).toMatchObject({
kind: "scanned",
@@ -145,7 +168,7 @@ describe("ordinary Bash and Zsh syntax", () => {
test.each([
["coproc job { scan_probe; }", ["scan_probe"]],
["printf '%s' @(one|$(scan_probe))", ["printf", "scan_probe"]],
["case x in @(one|$(scan_probe))) ;; esac", ["scan_probe"]],
["printf '%s' $((1 + '$(scan_probe)'))", ["printf", "scan_probe"]],
["printf '%s' $(((1 + '$(scan_probe)')))", ["printf", "scan_probe"]],
['printf %s "${ scan_probe; }"', ["printf", "scan_probe"]],
@@ -241,3 +264,56 @@ describe("Bash shared heredoc delimiter grammar", () => {
)
})
})
describe("Bash dialects", () => {
const heads = (source: string, dialect?: ShellScan.Dialect) => {
const result = ShellScan.scan(source, dialect)
return result.kind === "scanned" ? result.commands.map((command) => command.words[0]) : result.kind
}
test("posix reports both readings of a double parenthesis", () => {
expect(heads("(( x = 1 )); y", "bash")).toEqual(["y"])
expect(heads("(( x = 1 )); y", "zsh")).toEqual(["y"])
expect(heads("(( x = 1 )); y", "posix")).toEqual(["x", "y"])
expect(heads("(( x = 1 )); y")).toEqual(["x", "y"])
expect(heads("(( (1) + (2) ))", "bash")).toEqual([])
expect(heads("(( (1) + (2) ))", "posix")).toBe("opaque")
})
test.each([
["true &>/dev/null next", ["true"], "opaque"],
["X=$[1 + 2] next", ["next"], "opaque"],
] as const)("reads Bash and Zsh syntax precisely where Dash diverges: %s", (source, precise, posix) => {
expect(heads(source, "bash")).toEqual([...precise])
expect(heads(source, "zsh")).toEqual([...precise])
expect(heads(source, "posix")).toEqual(posix)
})
test("reads a reserved word after a redirect as a Zsh keyword", () => {
const source = "if true; then >/dev/null fi; next"
expect(heads(source, "zsh")).toEqual(["true", "next"])
expect(heads(source, "bash")).toBe("opaque")
expect(heads(source, "posix")).toBe("opaque")
})
test("scans safe Zsh parameter flags, Zsh repeat loops, and Bash extglob arguments", () => {
expect(heads("print -l ${(M)files:#*.ts}", "zsh")).toEqual(["print"])
expect(heads("print -l ${(ps:\\n:)text}", "zsh")).toEqual(["print"])
expect(heads("print -r -- ${(q-)x} ${(q+)x} ${(on-)x}", "zsh")).toEqual(["print"])
expect(heads("repeat 3; do echo hi; done", "zsh")).toEqual(["echo"])
expect(heads("repeat 3; do echo hi; done", "posix")).toEqual(["echo"])
expect(heads("f() repeat 3; do echo hi; done", "zsh")).toEqual(["echo"])
expect(heads("ls @(foo|bar)", "bash")).toEqual(["ls"])
})
test.each([
["/bin/bash", ["y"]],
["/usr/local/bin/zsh", ["y"]],
["/bin/sh", ["x = 1", "y"]],
["/bin/dash", ["x = 1", "y"]],
["/opt/bin/mksh", ["x = 1", "y"]],
] as const)("derives the dialect from the shell executable: %s", async (shell, resources) => {
const result = await Effect.runPromise(ShellParse.scanPortable("(( x = 1 )); y", shell, "/workspace"))
expect(result.commands.map((command) => command.resource)).toEqual([...resources])
})
})
@@ -0,0 +1,751 @@
import { afterAll, describe, expect, test } from "bun:test"
import { Effect, Exit } from "effect"
import fs from "fs"
import os from "os"
import path from "path"
import { ShellParse } from "../../src/shell/parse.js"
import { ShellScan } from "../../src/shell/scan.js"
const shellCandidates = ["/bin/bash", "/opt/homebrew/bin/bash", "/usr/local/bin/bash", "bash", "zsh", "dash"]
const shells = [
...new Set(
shellCandidates
.map((item) => (item.startsWith("/") ? (fs.existsSync(item) ? item : undefined) : Bun.which(item)))
.filter((item): item is string => Boolean(item)),
),
]
// Fixtures target specific shells and versions (bash 3.2 and 5.3, zsh, dash). Set SHELL_ORACLE_STRICT=1 on a
// machine with all of them to also verify that every fixture still executes somewhere.
const strict = process.env.SHELL_ORACLE_STRICT === "1"
const root = fs.mkdtempSync(path.join(os.tmpdir(), "opencode-shell-oracle-"))
const bin = path.join(root, "bin")
const target = path.join(root, "target")
const log = path.join(root, "log")
fs.mkdirSync(bin)
fs.mkdirSync(target)
fs.writeFileSync(
path.join(bin, "scan_probe"),
'#!/bin/sh\nprintf \'%s\\n\' "scan_probe${1:+ $*}" >> "$SCAN_PROBE_LOG"\n',
{
mode: 0o755,
},
)
// Dash runs `scan_probe[x y]=1` as the command `scan_probe[x`.
fs.copyFileSync(path.join(bin, "scan_probe"), path.join(bin, "scan_probe[x"))
// Zsh treats a trailing parenthesized group after an existing file name as glob qualifiers.
fs.writeFileSync(path.join(root, "a="), "")
afterAll(() => fs.rmSync(root, { recursive: true, force: true }))
function shellFlags(executable: string) {
if (executable.endsWith("/bash")) return ["--noprofile", "--norc"]
if (executable.endsWith("/zsh")) return ["-f"]
return []
}
// Runs source in a real shell and returns the lines it logged.
function observe(executable: string, source: string) {
fs.writeFileSync(log, "")
Bun.spawnSync([executable, ...shellFlags(executable), "-c", source], {
cwd: root,
env: { PATH: `${bin}:/usr/bin:/bin`, HOME: root, LC_ALL: "C", SCAN_PROBE_LOG: log },
timeout: 2_000,
})
return fs
.readFileSync(log, "utf8")
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
}
// Each dialect must report what its shells run; posix, the default, covers every shell.
const dialects = {
bash: (executable: string) => path.basename(executable) === "bash",
zsh: (executable: string) => path.basename(executable) === "zsh",
posix: () => true,
} satisfies Record<ShellScan.Dialect, (executable: string) => boolean>
function expectProbesReported(source: string) {
const runs = shells.map((executable) => [executable, observe(executable, source)] as const)
if (strict)
expect(
runs.some(([, invocations]) => invocations.length > 0),
`Fixture never executed scan_probe in any real shell: ${source}`,
).toBe(true)
expect(ShellScan.scan(source)).toEqual(ShellScan.scan(source, "posix"))
for (const [dialect, runsIn] of Object.entries(dialects)) {
const result = ShellScan.scan(source, dialect as ShellScan.Dialect)
if (result.kind === "opaque") continue
const reported = result.commands.filter((command) => !command.declaration).map((command) => command.words.join(" "))
for (const [executable, invocations] of runs.filter(([executable]) => runsIn(executable)))
for (const invocation of invocations)
expect(reported, `${executable} executed ${invocation} with ${dialect} in: ${source}`).toContain(invocation)
}
}
// Each fixture runs scan_probe in at least one real shell, which the scanner must report or reject.
const fixtures = [
"a=(1)scan_probe",
"a=(\n1)scan_probe; ",
"a=(1)'scan_probe'",
"alias p=scan_probe\np",
"command alias p=scan_probe\np",
"x='*(e:scan_probe:)'; echo $^~x",
"x='$(scan_probe)'; echo \"${\\\n(e)x}\"",
"x='$(scan_probe)'; echo ${(j:):e)x}",
"x='*(e:scan_probe:)'; echo ${(f)~x}",
"x='*(e:scan_probe:)'; echo ${=~x}",
"x='*(e:scan_probe:)'; echo $=~x",
"x='$(scan_probe)'; echo ${(%e)x}",
"x='$(scan_probe)'; echo ${(s.:.e)x}",
"x='$(scan_probe)'; echo ${(s(:)e)x}",
"x='$(scan_probe)'; echo ${(ej: :)x}",
"x='*(e:scan_probe:)'; echo ${(s: :)~x}",
"x='*(e:scan_probe:)'; echo ${^~x}",
"printf -v x 'a[$(scan_probe)]'; echo $((x))",
"a=(1); getopts a: x -a 'a[$(scan_probe)]'; echo $((OPTARG))",
"command -- declare 'a[$(scan_probe)]=1'",
"a=(1); unset 'a[b[$\\\n(scan_probe)0]]'",
'declare \'a["\\"]"$(scan_probe)0]=1\'',
"declare -a 'a=(+ [$(scan_probe)]=1)'",
"a=(1); echo ${a[b[\\$(scan_probe)]]}",
'a["b[\\$(scan_probe)]"]=1',
'b=1; a["b[\\$(scan_probe)1]"]=1',
"a=(1); echo $[ ${x:-a[\\$(scan_probe)1]} ]",
"a=(1); s=abc; echo ${s:'a[$(scan_probe)0]'}",
"a=(1); s=abc; echo ${s:${x:-'a[$(scan_probe)1]'}}",
"read x <<< 'a[$(scan_probe f)]'; echo $((x))",
"mapfile -t a <<< 'b[$(scan_probe g)]'; echo $((a[0]))",
"x=1; echo $(( x[\\$(scan_probe i)] ))",
"a=(1); echo ${a[@]:'a[$(scan_probe k)]'}",
"builtin -- declare 'a[$(scan_probe)]=1'",
"command -p declare 'a[$(scan_probe)]=1'",
'echo "${unset:+${x[\'"\']}}"]}} \'$(scan_probe)\' " # "',
"echo \"${unset:+${x['}}\"']}}'; scan_probe # \"",
"echo \"${x:-$'\\''}\"; scan_probe # '\"",
"cat <<E\n${x:-'}'}' $(scan_probe)\nE",
'echo "${x:-a\'b}c\'d}"$(scan_probe)"\'"',
"echo \"${PATH//:/$'\\n'}\"; scan_probe ok",
"echo $(( : # ))'\n); scan_probe ) # '",
"echo $(( $(echo 1) # ))'\n); scan_probe ) # '",
"(( : # ))'\n); scan_probe ) # '",
"echo $(( 16#ff + 2#1 + $# + ${#x} )); scan_probe ok",
"(( scan_probe ))",
"(( (scan_probe) & (scan_probe) ))",
"(( (scan_probe)\n(scan_probe) ))",
"cat <<'}'; {\n:\n}\nscan_probe; cat <<'}'; }\n}",
"cat <<'x)'; case x in\nx)\nx) scan_probe; cat <<'x)'\nx)\n;; esac",
"cat <<'if'; f()\nif\nif scan_probe; cat <<'if'\nif\ntrue; then :; fi; f",
"cat <<E; ( true\nE\nscan_probe g\n)",
"cat <<E; { true\nE\nscan_probe g\n}",
"cat <<E; f() { true\nE\nscan_probe f\n}; f",
"cat <<E; if true\nE\nscan_probe i\nthen :; fi",
"cat <<E; echo $(true\nscan_probe s\n)\nE\nscan_probe after",
"cat <<E; echo `true\nscan_probe b\n`\nE\nscan_probe after",
"cat <<E; cat <(true\nscan_probe s\n)\nE\nscan_probe after",
"cat <<E; x=$(cat <<F\nF\n)\nE\nscan_probe out",
"{ cat <<E; }\nscan_probe x\nE\nscan_probe y",
"[[ a]]b# ]] && scan_probe",
'[[ "a]]"# ]] && scan_probe',
"[[ 'a]]'# ]] && scan_probe",
"[[ a]]b = a]]b ]] && scan_probe",
'{ export X="a"{}# ; scan_probe; }',
"{ export X=a}# ; scan_probe; }",
"{ case esac in (esac) scan_probe;; esac\n}",
"unset a[b\nscan_probe\necho ]+=1",
"export a[b; scan_probe; echo ]=1",
"a[b; scan_probe; echo ]=1",
"a[0 #]\n]=1; scan_probe",
"for i in 1; { scan_probe; }; while false; do export X=1; done",
"for ((i=0; i<1; i++)); { scan_probe; }; while false; do export X=1; done",
"for i in 1; scan_probe",
"((( echo '\"' ); scan_probe; ( echo '\"' )))",
"set=1; export X=${set:-${x['0\"0']}}; scan_probe; : '\"]}}' # '",
"export X=$$$$'\\'; scan_probe # '",
'export X="`export Y=\\"\'\\" ; scan_probe; export Z=\\"\'\\"`"',
'export X="`echo \\"(\\"; scan_probe; echo \\")\\"`"',
": <<-export\n\tex\\\n\tport\n$(scan_probe)\nexport",
'export X="${\\\n scan_probe; }"',
'export X="${\n scan_probe; }"',
'export X="${|\\\n REPLY=$(scan_probe); }"',
"!(scan_probe; true)",
"if !(scan_probe); then :; fi",
"f+() { scan_probe; }; f+",
': <<$"export"\n$export\nscan_probe\nexport',
': <<$"export"\nexport\nscan_probe\n$export',
"export X=$[1; scan_probe; : ]",
"X=$[1 scan_probe ]",
'export X=$["]"]; scan_probe # ]',
"export X=${#}# ; scan_probe",
"export X=$#a# ; scan_probe",
"export X=~+# ; scan_probe",
"{ export X=1; }# ; scan_probe; }",
"if true; then export X=1; fi# ; scan_probe; fi",
"for i in 1; do export X=1; done# ; scan_probe; done",
"case esac# in x) export X=1;; esac#) scan_probe;; esac",
": <<A <<B\nA\n$(scan_probe)\nB",
'export X="$(cat <<EOF\n)\nEOF\nscan_probe)"',
"cat <<EOF | scan_probe\nhello\nEOF",
"cat <<EOF && scan_probe\nhello\nEOF",
"cat <<'E'\"O\"F\n# $(scan_probe)\nEOF\nscan_probe",
"cat <<E\\\nOF\n$(scan_probe)\nEOF",
"cat <<-EOF\n\t EOF\n$(scan_probe)\n\tEOF",
"cat <<export\nex\\\nport\n# $(scan_probe)\nexport",
"cat <<< 'a'\"$(scan_probe)\"",
'export X=${x:-"}"}; scan_probe',
"export X=${x:-'}'}; scan_probe",
'export X="${x:-"}"}"; scan_probe',
'export X="${x:-"$(scan_probe)"}"',
"export X=${x//\\}/}; scan_probe; : }",
'export X="${x//\\}/}"; scan_probe; : "}"',
"export X=${x#${y}}; scan_probe",
'export X="${x#${y:-"}"}}"; scan_probe',
"export X=${!prefix*}; scan_probe",
"export X=${x@Q}; scan_probe",
"export X=\"${unset:+${x['\"']}}\"; scan_probe; : '\"}]}'",
'export X="${unset:+${x[\'}}\"; scan_probe; : \"\']}}"',
'echo $(case x in x) echo ")" ;; esac; scan_probe)',
"echo \"$(case x in (x) echo ')' ;; esac; scan_probe)\"",
'echo "`echo \\"$(scan_probe)\\"`"',
"echo `echo \\`scan_probe\\``",
"echo $( (echo a); scan_probe )",
"echo $((echo a); scan_probe)",
"if true; then scan_probe; elif true; then :; else :; fi",
"for i in in do done esac; do scan_probe; done",
"for do in 1; do scan_probe; done",
"for in in 1; do scan_probe; done",
"for x\nin 1; do scan_probe; done",
"set -- 1; for x\ndo scan_probe; done",
"set -- 1; for x do scan_probe; done",
"case in in in) scan_probe;; esac",
"case esac in a|esac) scan_probe;; esac",
"case y in x) ;; y) scan_probe;; esac",
"case x in x) scan_probe ;& y) : ;;& z) : ;; esac",
"case x in x) scan_probe ;| y) : ;; esac",
"case [ in [) scan_probe & ( scan_probe q ]) ;; esac",
"case x in (x|[) scan_probe & ( scan_probe q ]) ;; esac",
"while false; do :; done & scan_probe",
"{ scan_probe & }",
"( scan_probe & )",
"[[ b =~ b ]] && scan_probe",
"[[ ( a == a ) && ( b == b ) ]] && scan_probe",
'[[ "$(scan_probe)" == "]]" ]]',
"(( 1 + $(scan_probe) ))",
"(( a = 1 )) && scan_probe",
"(( (1) + (2) )); scan_probe",
"(( (echo a); scan_probe ))",
">/dev/null scan_probe",
"2>&1 scan_probe",
"A=1 >/dev/null B=2 scan_probe",
'export A=1 B="$(scan_probe)"',
'declare -a arr=(1 "$(scan_probe)")',
"export X=$'a'\\\n; scan_probe",
"case x in \\\nx) \\\nscan_probe;; \\\nesac",
"if true; then \\\nscan_probe; fi",
'() { :; } "$(scan_probe)"',
"function f() ( scan_probe ); f",
"{ export X=1 } && scan_probe",
"{ export X=1 } ; scan_probe ; }",
// Dialects disagree about reserved words after redirects and operators split by line continuations.
"if true; then >/dev/null fi; scan_probe; fi",
"if true; then >/dev/null fi; scan_probe",
"case x in x) >/dev/null esac; scan_probe; esac",
"true &>/dev/null scan_probe",
"true &>>/dev/null scan_probe",
"true &\\\n>/dev/null scan_probe",
"cat <<\\\n-EOF\nEOF\nscan_probe h1\n-EOF",
"cat <<\\\n-EOF\n-EOF\nscan_probe z\nEOF",
"cat <\\\n(scan_probe i)",
"[[ -n <\\\n(scan_probe c1) ]]",
"a=(<\\\n(scan_probe a1))",
"cat <<E\n$\\\n(scan_probe h)\nE",
"echo ${x:-$\\\n(scan_probe p)}",
"(( $\\\n(scan_probe a) ))",
"[[ $\\\n(scan_probe c) ]]",
"a=($\\\n(scan_probe arr))",
// Dash splits assignment subscripts at blanks.
"scan_probe[x y]=1",
"a[x '$(scan_probe)']=1",
"a[1 + $(scan_probe)]=1",
// Expansions inside parameter words, arithmetic, and subscripts.
"echo \"${x:-$'$(scan_probe q1)'}\"",
"echo ${x:-<(scan_probe p1)}",
"echo ${x:->(scan_probe g)}",
"x=${y:-<(scan_probe p4)}",
"[[ x == ${y:-<(scan_probe p5)} ]]",
"echo $(( $'$(scan_probe a)' ))",
"(( x = $'$(scan_probe b)' ))",
"echo $(( ${x:-'$(scan_probe a)'} ))",
"(( ${x:-'$(scan_probe b)'} ))",
"a[${x:-'$(scan_probe c)'}]=1",
"echo $[ ${x:-'$(scan_probe d)'} ]",
"echo $[ $'$(scan_probe h)' ]",
"echo ${a[${x:-'$(scan_probe e)'}]}",
"echo \"${a[${x:-'$(scan_probe f)'}]}\"",
"cat <<E\n${x:-'$(scan_probe h)'}\nE",
// Zsh glob qualifiers and extglob groups run code in globbed words.
"echo @(<(scan_probe e1))",
"printf '%s' @(one|$(scan_probe))",
"echo *(e:'scan_probe q1':)",
"echo *(+scan_probe)",
"a=(*(e:'scan_probe g':))",
"declare -a a=(*(e:'scan_probe g':))",
"export a=(*(e:'scan_probe h':))",
"for f in *(e:'scan_probe h':); do :; done",
"echo ${x:-target(e:'scan_probe p2':)}",
"echo ${x:-*(e:'scan_probe j':)}",
"echo a=(e:'scan_probe p3':)",
"echo >*(e:'scan_probe f':)",
"cat <*(e:'scan_probe g':)",
"echo $x*(e:'scan_probe h':)",
"echo \"\"*(e:'scan_probe i':)",
"echo {a,*(e:'scan_probe m':)}",
// Builtins and arithmetic evaluate subscripts in decoded literal text.
"declare -i x='a[$(scan_probe)]'",
"declare 'a[$(scan_probe)]=1'",
"declare -a 'a=([$(scan_probe)]=1)'",
"a=(1); unset 'a[$(scan_probe)]'",
'a=(1); unset "a[\\$(scan_probe)]"',
"[[ 'a[$(scan_probe)]' -eq 1 ]]",
"[[ -v 'a[$(scan_probe)]' ]]",
"read 'a[$(scan_probe)]' </dev/null",
"printf -v 'a[$(scan_probe)]' x",
"x='a[$(scan_probe)]'; echo $((x))",
"s=abc; x='a[$(scan_probe)0]'; printf '%s' \"${s:x}\"",
"ref='x[$(scan_probe)0]'; printf '%s' \"${!ref}\"",
"declare ${x:-'a[$(scan_probe)]=1'}",
'declare "${x:-a[\\$(scan_probe)]=1}"',
"read ${x:-'a[$(scan_probe)]'} </dev/null",
"declare \"$(echo 'a[$(scan_probe)]=1')\"",
"declare \"${ echo a; }\"'[$(scan_probe)]=1'",
"n=a; declare \"$n\"'[$(scan_probe)]=1'",
"declare \"$(echo a)\"'[$(scan_probe)]=1'",
"builtin declare 'a[$(scan_probe)]=1'",
"printf -v'a[$(scan_probe)]' x",
"set -- 'a[$(scan_probe)]'; echo $(($1))",
"for x in 'a[$(scan_probe)]'; do echo $((x)); done",
"a=(1 'a[$(scan_probe)]'); echo $((a[1]))",
"x='a[$(scan_probe)]' eval 'echo $((x))'",
"a=(1); echo $(( a[\\$(scan_probe)] ))",
"a=(1); (( a[\\$(scan_probe)] ))",
// Explicit evaluation operators.
"x='$(scan_probe)'; echo ${x@P}",
"x='$(scan_probe)'; echo \"${x@P}\"",
"x='$(scan_probe)'; echo ${(e)x}",
"x='$(scan_probe)'; echo ${(ee)x}",
"echo ${(e):-'$(scan_probe)'}",
"x='*(e:scan_probe:)'; echo ${~x}",
"x='*(e:scan_probe:)'; echo $~x",
// Precommand modifiers, bundled/ordered binding options, repeat, compgen, and setopt.
"noglob typeset 'a[$(scan_probe)]=1'",
"nocorrect typeset 'a[$(scan_probe)]=1'",
"a=(1); noglob unset 'a[$(scan_probe)]'",
"noglob read 'a[$(scan_probe)]' <<< x",
'a=(1); noglob let "a[\\$(scan_probe)]"',
"print -r -v 'a[$(scan_probe)]' x",
"print -rv 'a[$(scan_probe)]' x",
"a=(1); print -r -v x 'a[$(scan_probe)]'; echo $((x))",
"a=(1); print -rv x 'a[$(scan_probe)]'; echo $((x))",
": & wait -np 'a[$(scan_probe)]'",
": & wait -n -p'a[$(scan_probe)]'",
": & wait -fp 'a[$(scan_probe)]' $!",
"time declare 'a[$(scan_probe)]=1'",
"coproc declare 'a[$(scan_probe)]=1'",
"a=(1); time unset 'a[$(scan_probe)]'",
"a=(1); time let 'a[$(scan_probe)]'",
"a=(1); time [[ -v 'a[$(scan_probe)]' ]]",
"a=(1); time [[ 'a[$(scan_probe)]' -eq 1 ]]",
"a=(1); coproc [[ -v 'a[$(scan_probe)]' ]]",
'export X="${ time ! { :; }; scan_probe; }"',
'export X="${ time\\\n { :; }; scan_probe; }"',
'export X="${ time -- { :; }; scan_probe; }"',
"repeat 1 scan_probe",
"a=(1); repeat 'a[$(scan_probe)]' :",
"setopt globsubst; x='*(e:scan_probe:)'; echo $x",
"set -o globsubst; x='*(e:scan_probe:)'; echo $x",
"compgen -C scan_probe",
"compgen -C 'scan_probe'",
// Combined literal/output subscripts, deferred bindings, continuations, Zsh flags, and ANSI-C escapes.
"declare \"a[$(echo '$(scan_probe)')]=1\"",
"declare \"$(echo 'a[')\"'$(scan_probe)]=1'",
"a=(1); unset \"a[$(echo '$(scan_probe)')]\"",
"a=(1); echo $(( a[$(echo '$(scan_probe)')] ))",
"x='$(scan_probe)'; declare \"a[$x]=1\"",
"x='$(scan_probe)'; a=(1); unset \"a[$x]\"",
"x='$(scan_probe)'; let \"a[$x]=1\"",
"x='$(scan_probe)'; read \"a[$x]\" <<< 1",
"x='$(scan_probe)'; printf -v \"a[$x]\" 1",
"a=(1); x='$(scan_probe)'; echo $((a[$x]))",
"a=(1); x='$(scan_probe)'; [[ -v \"a[$x]\" ]]",
"a=(1); x='$(scan_probe)'; [[ \"a[$x]\" -eq 1 ]]",
"a=(1); echo ${a\\\n['$(scan_probe)']}",
"a=(1); s=abc; echo ${s\\\n:'a[$(scan_probe)0]'}",
"a=(1); echo ${a[0]\\\n:'a[$(scan_probe)0]'}",
"x='$(scan_probe)'; echo ${x@\\\nP}",
"x='$(scan_probe)'; echo \"${x@\\\nP}\"",
"x='$(scan_probe)'; echo ${x@P\\\n}",
"x='*(e:scan_probe:)'; echo $\\\n~x",
"x='*(e:scan_probe:)'; echo $^\\\n~x",
"x='*(e:scan_probe:)'; echo ${^\\\n~x}",
"x='*(e:scan_probe:)'; echo ${(f)\\\n~x}",
"a=(1); echo ${(f)a['a[$(scan_probe)]']}",
"a=(1); echo ${=a['a[$(scan_probe)]']}",
"a=(1); echo ${^a['a[$(scan_probe)]']}",
"a=(1); s=abc; echo ${(f)s:'a[$(scan_probe)0]'}",
"a=(1); s=abc; echo ${=s:'a[$(scan_probe)0]'}",
"a=(1); s=abc; echo ${^s:'a[$(scan_probe)0]'}",
"a=(1); [[ x != *(a)]] && 0 -eq 'a[$(scan_probe)]' ]]",
"a=(1); [[ x != *(a)]] && -v 'a[$(scan_probe)]' ]]",
"a=(1); [[ x != (a)]] && 0 -eq 'a[$(scan_probe)]' ]]",
"shopt -s extglob\na=(1); [[ x != *(a)]] && 0 -eq 'a[$(scan_probe)]' ]]",
"a=(1); echo $(( $'a\\x5b\\x24\\x28scan_probe\\x29\\x5d' ))",
"a=(1); (( $'a\\x5b\\x24\\x28scan_probe\\x29\\x5d' ))",
"a=(1); echo ${a[$'a\\x5b\\x24\\x28scan_probe\\x29\\x5d']}",
"a=(1); declare \"${x:-$'a\\x5b\\x24\\x28scan_probe\\x29\\x5d=1'}\"",
"a=(1); unset \"${x:-$'a\\x5b\\x24\\x28scan_probe\\x29\\x5d'}\"",
"(( $'\\' )) # ' )); scan_probe",
"declare -A a; a[$'\\']=1 # ']=1; scan_probe",
"a=(1); echo ${a[$'\\']} # ']} $(scan_probe)",
": <<$'export'\n$export\nscan_probe\nexport",
": <<$'EOF'\n$EOF\nscan_probe\nEOF",
"echo $'\\'; scan_probe; : ' # '",
"export X=$'\\'; scan_probe; : ' # '",
"export X=$'\\c\\\\'; scan_probe # '",
"export X=$'\\c'; scan_probe; : ' # '",
"$'scan_probe\\0x'",
"$'scan_probe\\x00x'",
"$'scan_probe\\u0000x'",
"$'scan_probe\\c@x'",
"$'declare\\0x' 'a[$(scan_probe)]=1'",
"a=(1); $'unset\\0x' 'a[$(scan_probe)]'",
"$'let\\0x' 'a[$(scan_probe)]'",
"x='x y'; scan_probe[$x]=1",
// Parameter default bindings, Zsh subscripts, precommands, namerefs, ANSI-C escapes, and Zsh builtins.
"a=(1); : ${x:='a[$(scan_probe)]'}; echo $((x))",
"a=(1); : ${x:='$(scan_probe)'}; echo $((a[$x]))",
"a=(1); : ${x='$(scan_probe)'}; unset \"a[$x]\"",
'a=(1); : "${x:="a[\\$(scan_probe)]"}"; echo $((x))',
"a=(); : ${a[0]:='a[$(scan_probe)]'}; echo $((a[0]))",
"declare -i a x; : ${x:='a[$(scan_probe)]'}",
"a=(1); echo ${@[1,'a[$(scan_probe)]']}",
"a=(1); echo ${*[1,'a[$(scan_probe)]']}",
"a=(1); echo ${1[1,'a[$(scan_probe)]']}",
"a=(1); echo ${a[1]['$(scan_probe)']}",
"a=(1); echo ${s[1][1]:'a[$(scan_probe)0]'}",
"a=(1); echo ${${a}['$(scan_probe)']}",
"a=(1); echo ${${s}:'a[$(scan_probe)0]'}",
"a=(1); echo $a['$(scan_probe)']",
'a=(1); echo "$a[a[\\$(scan_probe)]]"',
"a=(1); x='$(scan_probe)'; echo $a[a[$x]]",
"a=(1); x='$(scan_probe)'; echo \"$a[a[$x]]\"",
"a=(1); echo $@[1,'a[$(scan_probe)]']",
"a=(1); echo $^a['$(scan_probe)']",
"exec typeset 'a[$(scan_probe)]=1'",
"a=(1); - typeset 'a[$(scan_probe)]=1'",
"a=(1); exec unset 'a[$(scan_probe)]'",
"a=(1); - unset 'a[$(scan_probe)]'",
"a=(1); exec -a foo unset 'a[$(scan_probe)]'",
"a=(1); exec let 'a[$(scan_probe)]'",
"a=(1); - let 'a[$(scan_probe)]'",
"unset a=(e:'scan_probe':)",
"unsetenv a=(e:'scan_probe':)",
"a=(1); r='a[$(scan_probe)]'; declare -n r; r=1",
"a=(1); r='a[$(scan_probe)]'; declare -n r; echo $r",
"a=(1); r='a[$(scan_probe)]'; typeset -n r; r=1",
"a=(1); x='a[$(scan_probe)]'; (( $'\\170' ))",
"a=(1); x='a[$(scan_probe)]'; echo ${a[$'\\170']}",
"a=(1); x='a[$(scan_probe)]'; declare a[x]=1",
"a=(1); x='a[$(scan_probe)]'; declare 'a[x]=1'",
"a=(1); x='a[$(scan_probe)]'; unset 'a[x]'",
"a=(1); x='a[$(scan_probe)]'; [[ -v a[x] ]]",
"a=(1); x='a[$(scan_probe)]'; read 'a[x]' <<< 1",
"a=(1); x='a[$(scan_probe)]'; printf -v 'a[x]' 1",
"a=(1); x='a[$(scan_probe)]'; test -v 'a[x]'",
"a=(1); echo ${(s:):)a['a[$(scan_probe)]']}",
"a=(1); s=abc; echo ${(s:):)s:'a[$(scan_probe)0]'}",
"a=(1); [[ !(-v 'a[$(scan_probe)]') ]]",
"echo $'a\\'; scan_probe; ' \\'",
"echo $'\\'$(scan_probe)' \\'",
": <<$'a\\'; scan_probe; '\\'\n$a\\\n\na'; scan_probe; '",
"export X=$'\\c\\''; scan_probe # '",
"a=(1); declare $'a[\\0$(scan_probe)]=1'",
"a=(1); unset $'a[\\0$(scan_probe)]'",
"a=(1); declare $'a[\\u0000$(scan_probe)]=1'",
": <<$'\\u0041'\n\\u0041\nscan_probe\nA",
": <<$'\\U00000041'\n\\U00000041\nscan_probe\nA",
"$'s\\can_probe'",
"$'de\\clare' 'a[$(scan_probe)]=1'",
"$'\\scan_probe'",
"a=(1); typeset $'a\\[\\$\\(scan_probe\\)\\]=1'",
"$'scan_probe\\xz'",
"$'scan_probe\\uz'",
"$'scan_probe\\Uz'",
"$'scan_probe\\C-@x'",
"$'scan_probe\\C@x'",
"a=(1); set -A 'a[$(scan_probe)]' 1",
"a=(1); set +A 'a[$(scan_probe)]' 1",
"unsetopt noglobsubst; x='*(e:scan_probe:)'; echo $x",
"set +o noglobsubst; x='*(e:scan_probe:)'; echo $x",
"set -xo globsubst; x='*(e:scan_probe:)'; echo $x",
"emulate zsh -o globsubst; x='*(e:scan_probe:)'; echo $x",
"NULLCMD=scan_probe; >/dev/null",
"READNULLCMD=scan_probe; </dev/null",
"NULLCMD=scan_probe; <<< x",
"setopt promptsubst; x='$(scan_probe)'; print -P $x",
"setopt promptvars; print -P '$(scan_probe)'",
"a=(1); exit 'a[$(scan_probe)]'",
"f() { return 'a[$(scan_probe)]'; }; a=(1); f",
"a=(1); shift 'a[$(scan_probe)]'",
"a=(1); for x in 1; do break 'a[$(scan_probe)]'; done",
"a=(1); for x in 1; do continue 'a[$(scan_probe)]'; done",
"a=(1); bye 'a[$(scan_probe)]'",
"a=(1); logout 'a[$(scan_probe)]'",
"a=(1); getln 'a[$(scan_probe)]'",
"a=(1); vared 'a[$(scan_probe)]'",
"a=(1); zformat -f 'a[$(scan_probe)]' x",
"a=(1); zstyle -s : x 'a[$(scan_probe)]'",
"zmodload zsh/stat; a=(1); zstat -A 'a[$(scan_probe)]' .",
"a=(1); compgen -V x -W 'a[$(scan_probe)]'; echo $((x[0]))",
// Special/positional subscripts, escape decoders, split expansions, float/array declarations, and continuations.
"a=(1); set -- 1 2 3 4 5 6 7 8 9 abc; echo ${1\\\n0:'a[$(scan_probe)0]'}",
"a=(1); echo ${1\\\n0[1,'a[$(scan_probe)]']}",
"a=(1); echo $0[1,'a[$(scan_probe)]']",
"a=(1); echo $?[1,'a[$(scan_probe)]']",
"a=(1); echo $#[1,'a[$(scan_probe)]']",
"a=(1); echo $-[1,'a[$(scan_probe)]']",
"a=(1); echo $![1,'a[$(scan_probe)]']",
"a=(1); echo $$[1,'a[$(scan_probe)]']",
'a=(1); echo "$?[1,a[\\$(scan_probe)]]"',
'a=(1); echo "$$[1,a[\\$(scan_probe)]]"',
"a=(1); x='a[\\x24(scan_probe)]'; unset \"${x@E}\"",
"a=(1); x='a[\\x24(scan_probe)]'; declare \"${x@E}=1\"",
"a=(1); x='a[\\x24(scan_probe)]'; y=${x@E}; echo $((y))",
'a=(1); x="$\'a[\\\\x24(scan_probe)]\'"; unset "${(Q)x}"',
'a=(1); x="$\'a[\\\\x24(scan_probe)]\'"; typeset "${(Q)x}=1"',
"a=(1); x=\"$'a[\\\\x24(scan_probe)]'\"; y=${(Q)x}; echo $((y))",
"a=(1); x='a[$'; x+='(scan_probe)]'; unset \"$x\"",
'a=(1); d=\'$\'; x="a[${d}(scan_probe)]"; unset "$x"',
"a=(1); x='a[$_1(scan_probe)]'; unset \"${x/_1/}\"",
"a=(1); x='a[$_1(scan_probe)]'; echo $(( ${x/_1/} ))",
"a=(1); x='a[$'; y='(scan_probe)]'; echo $(( $x$y ))",
"a=(1); read x <<< 'a[$\\(scan_probe)]'; echo $((x))",
"a=(1); printf -v x 'a[\\x24(scan_probe)]'; echo $((x))",
"a=(1); print -v x 'a[\\x24(scan_probe)]'; echo $((x))",
"a=(1); typeset -E x; x='a[$(scan_probe)]'",
"a=(1); typeset -F x; x='a[$(scan_probe)]'",
"a=(1); y='a[$(scan_probe)]'; typeset -E x=y",
"a=(1); y='a[$(scan_probe)]'; typeset -F x=y",
"a=(); declare a='([$(scan_probe)]=1)'",
"a=(); typeset a='([$(scan_probe)]=1)'",
"a=(); declare a+='([$(scan_probe)]=1)'",
"f() { local -a a; local a='([$(scan_probe)]=1)'; }; f",
"declare -a a; x='([$(scan_probe)]=1)'; declare a=$x",
"x=${NULLCMD::=scan_probe}; >/dev/null",
"x=${READNULLCMD::=scan_probe}; </dev/null",
": a${NULLCMD::=scan_probe}; >/dev/null",
": ${^NULLCMD::=scan_probe}; >/dev/null",
": ${(f)NULLCMD::=scan_probe}; >/dev/null",
"print -vNULLCMD scan_probe; >/dev/null",
"(( ${NULLCMD::=scan_probe} )); >/dev/null",
"[[ -n ${NULLCMD::=scan_probe} ]]; >/dev/null",
"a=(${NULLCMD::=scan_probe}); >/dev/null",
": <<EOF\n${NULLCMD::=scan_probe}\nEOF\n>/dev/null",
"PS4='$(scan_probe)'; set -x; x=1",
"PS4='$(scan_probe)'; set -o xtrace; x=1",
"export PS4='$(scan_probe)'; set -x; x=1",
"setopt -m 'glob*'; x='*(e:scan_probe:)'; echo $x",
"setopt -m '*subst*'; x='*(e:scan_probe:)'; echo $x",
"emulate sh; unsetopt shglob; setopt bareglobqual; x='*(e:scan_probe:)'; echo $x",
"emulate ksh; unsetopt shglob; setopt bareglobqual; x='*(e:scan_probe:)'; echo $x",
"a=(1); zregexparse 'a[$(scan_probe)]' 'a[$(scan_probe)]' x /x/",
"a=(1); private 'a[$(scan_probe)]=1'",
"a=(1); [[ x =~ (a )(b)]] || 0 -eq 'a[$(scan_probe)]' ]]",
"a=(1); [[ x =~ (a )(b)]] || -v 'a[$(scan_probe)]' ]]",
"a=(1); [[ -t 'a[$(scan_probe)]' ]]",
"a=(1); x='a[$(scan_probe)]'; [[ -t x ]]",
"a=(1); x='a[$(scan_probe)]'; [[ -t $x ]]",
"a=(1); test -t 'a[$(scan_probe)]'",
"a=(1); [ -t 'a[$(scan_probe)]' ]",
"a=(1); x='a[$(scan_probe)]'; test -t x",
"a=(1); x='a[$(scan_probe)]'; [ -t x ]",
"echo $\\\n'a\\''; scan_probe; : \\'",
": <<$\\\n'EOF'\nEOF\nscan_probe\n$EOF",
': <<$\\\n"EOF"\nEOF\nscan_probe\n$EOF',
"$\\\n'scan_probe'",
'$\\\n"scan_probe"',
"a=(1); unset $\\\n'a[\\x24(scan_probe)]'",
"echo \"${x:-$\\\n'\\x24(scan_probe)'}\"",
"[[ -n x ]\\\n] && scan_probe && [[ -n y ]]",
"a=(1); (\\\n( 'a[$(scan_probe)]' ))",
"a=(1); (\\\n( 1 + 'a[$(scan_probe)]' ))",
"a=(1); echo $(\\\n( 'a[$(scan_probe)]' ))",
"a=(1); x='a[$(scan_probe)]'; echo $(( x )\\\n)",
"a=(1); [\\\n[ -v 'a[$(scan_probe)]' ]]",
"a=(1); [\\\n[ 0 -eq 'a[$(scan_probe)]' ]]",
"a[0]\\\n=1 scan_probe",
"a[0]+\\\n=1 scan_probe",
"a=(); a[0]\\\n='a[$(scan_probe)]'; echo $((a[0]))",
"x='x y'; scan_probe[$x]\\\n=1",
"a=(1); x='a[$_1]'; y='(scan_probe)'; unset \"${x/_1/$y}\"",
"a=(1); x='a[$_1]'; y='(scan_probe)'; echo $(( ${x/_1/$y} ))",
"a=(1); x='a[$1'; y='(scan_probe)]'; echo $(( ${x%1}$y ))",
"a=(1); x='a[$1\n(scan_probe)]'; unset \"${x/1?/}\"",
"printf -v x 'a[\\x24\\x28scan_probe\\x29]'; echo $((x))",
"printf -v x 'a[\\044\\050scan_probe\\051]'; echo $((x))",
"a=(1); printf -v x 'a[\\u0024(scan_probe)]'; echo $((x))",
"a=(1); printf -v x 'a[\\U00000024(scan_probe)]'; echo $((x))",
"typeset -a a; print -v x 'a[\\x24\\x28scan_probe\\x29]'; echo $((x))",
"typeset -a a; print -v x 'a[\\044\\050scan_probe\\051]'; echo $((x))",
"typeset -a a; print -v x 'a[\\u0024\\u0028scan_probe\\u0029]'; echo $((x))",
"typeset -a a; print -v x 'a[\\U00000024\\U00000028scan_probe\\U00000029]'; echo $((x))",
'a=(1); x="$\'a[\\\\x24\\\\x28scan_probe\\\\x29]\'"; unset "${x:Q}"',
'a=(1); x="$\'a[\\\\x24\\\\x28scan_probe\\\\x29]\'"; unset "$x:Q"',
"a=(1); x=\"$'a[\\\\x24\\\\x28scan_probe\\\\x29]'\"; y=${x:Q}; echo $((y))",
"a=(1); read x <<'EOF'\na[$(scan_probe)]\nEOF\necho $((x))",
"a=(1); read x <<EOF\na[\\$(scan_probe)]\nEOF\necho $((x))",
"mapfile -t a <<'EOF'\nb[$(scan_probe)]\nEOF\necho $((a[0]))",
"a=(); declare a=\"$(echo '([$(scan_probe)]=1)')\"",
"a=(); declare a=\"`echo '([$(scan_probe)]=1)'`\"",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=$1",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=$@",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=$*",
"a=(); set -- '([$(scan_probe)]=1)'; declare a=\"$1\"",
"x==(scan_probe)",
"x+==(scan_probe)",
"a=(1); a[1]==(scan_probe)",
"a=(1); a[1]+==(scan_probe)",
"export x==(scan_probe)",
"typeset x==(scan_probe)",
"f() { local x==(scan_probe); }; f",
"x==\\\n(scan_probe)",
"a=(1); ref='a[$(scan_probe)]'; echo ${\\\n!ref}",
"a=(1); ref='a[$(scan_probe)]'; echo \"${\\\n!ref}\"",
"a=(1); set -k; f() { echo $((x)); }; f x='a[$(scan_probe)]'",
"a=(1); set -o keyword; f() { echo $((x)); }; f x='a[$(scan_probe)]'",
"a=(1); set -k; test -v 'a[x]' x='a[$(scan_probe)]'",
"a=(1); set -o posix -k; : x='a[$(scan_probe)]'; echo $((x))",
"set -oglobsubst; x='*(e:scan_probe:)'; echo $x",
"set +onoglobsubst; x='*(e:scan_probe:)'; echo $x",
"setopt -oglobsubst; x='*(e:scan_probe:)'; echo $x",
"setopt +onoglobsubst; x='*(e:scan_probe:)'; echo $x",
"printf -vPS4 '\\x24(scan_probe)'; set -x; x=1",
"printf -vPS4 '\\044(scan_probe)'; set -x; x=1",
"functions[zshexit]=scan_probe",
"functions[TRAPEXIT]=scan_probe",
"functions[TRAPDEBUG]=scan_probe; x=1",
"functions=(zshexit scan_probe)",
": ${functions[zshexit]::=scan_probe}",
"functions[echo]=scan_probe; echo hi",
"options[globsubst]=on; x='*(e:scan_probe:)'; echo $x",
"options=(globsubst on); x='*(e:scan_probe:)'; echo $x",
": ${options[globsubst]::=on}; x='*(e:scan_probe:)'; echo $x",
"aliases[echo]=scan_probe; options[aliases]=on; : `echo`",
"galiases[echo]=scan_probe; options[aliases]=on; : `echo`",
"saliases[x]=scan_probe; options[aliases]=on; : `a.x`",
"commands[ls]=$(command -v scan_probe); ls",
"a=(1); [[ x == \"\" || ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x != '' && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x != '' && ( -v 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x != '' && ( -t 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ == && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ -n == && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x == != || ( -t 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ x == == || ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"a=(1); [[ ( -n == ) && ( 0 -eq 'a[$(scan_probe)]' ) ]]",
"[[ -n $$'\\'' ]]; scan_probe # ' ]]",
"a=($$'\\''); scan_probe # ')",
"echo ${x:-$$'\\''}; scan_probe # '}",
"export X=$$\\\n'\\''; scan_probe # '",
"export X=$'\\'; scan_probe; : '; cat <<E\n'\nE",
"export X=$\\\n$'\\'; scan_probe; : ' # '",
'a=(1); echo "$a[(r)\\],a[\\$(scan_probe)]]"',
'a=(1); echo "$?[1\\],a[\\$(scan_probe)]]"',
'export X="${unset:+${a[0}}"; scan_probe; : "]}}"',
"export X=${unset:+${a[0}}; scan_probe; : ]}}",
'export X=${unset:+${a["0}"}}; scan_probe; : ]}}',
'cat <<E; export X="${unset:+$(( 0 + "0 ))}" && scan_probe\n))}"\nE',
'false && export X="${unset:+$(( 0 }"; scan_probe; : "))}"',
'false && export X="${unset:+$(echo a}"; scan_probe; : ")}"',
'false && export X="${unset:+$(echo {a}"; scan_probe; : ")}"',
'false && export X="${unset:+$(echo a})}"; scan_probe; : "}"',
"export X=$$'\\' # '; scan_probe",
"export X=$$'\\' #\nexport Y='; scan_probe # '",
"export X=$^$'\\' # '; scan_probe",
"export X=$#$'\\' # '; scan_probe",
"export X=$'\\'\nscan_probe\n'",
] as const
// These also run inside every wrapper below.
const nestedFixtures = [
"[[ a]]# ]] && scan_probe",
"{ export X={}# ; scan_probe; }",
"unset a[b; scan_probe; echo ]=1",
"case esac in (esac) scan_probe;; esac",
"case esac in (a|esac) scan_probe;; esac",
"for i in 1; { if true; then scan_probe; fi }; while false; do export X=1; done",
"(( echo '\"' ); scan_probe; ( echo '\"' ))",
"export X=${unset:+${x['0\"0']}}; scan_probe; : '\"]}}' # '",
"export X=$$'\\'; scan_probe # '",
": <<-export\n\tex\\\n\tport\n# $(scan_probe)\nexport",
"!(scan_probe)",
"f+() case x in x) scan_probe;; esac; f+",
"f@g() case x in x) scan_probe;; esac; f@g",
"f@g() for i in 1; do scan_probe; done; f@g",
] as const
const wrappers: Array<[name: string, wrap: (inner: string) => string]> = [
["$(...)", (inner) => `export OUTER=$( ${inner}\n)`],
['"$(...)"', (inner) => `export OUTER="$( ${inner}\n)"`],
["backticks", (inner) => `export OUTER=\` ${inner}\n\``],
["heredoc", (inner) => `: <<EOF\n$( ${inner}\n)\nEOF`],
["case arm", (inner) => `case x in x) ${inner}\n;; esac`],
["for loop", (inner) => `for k in 1; do ${inner}\ndone`],
["function", (inner) => `wrap_fn() {\n${inner}\n}; wrap_fn`],
["brace group", (inner) => `{ ${inner}\n}`],
]
describe.skipIf(process.platform === "win32")("real-shell soundness oracle", () => {
test("discovers at least bash on PATH", () => {
expect(shells.some((item) => item.endsWith("/bash"))).toBe(true)
})
test.each([...fixtures, ...nestedFixtures])("reports or rejects real-shell execution: %j", (source) => {
expectProbesReported(source)
})
test.each(wrappers.flatMap(([name, wrap]) => nestedFixtures.map((source) => [name, source, wrap(source)])))(
"reports or rejects in %s: %j",
(_, __, source) => {
expectProbesReported(source)
},
)
})
describe.skipIf(process.platform === "win32")("real-shell directory oracle", () => {
test.each(["cd >/dev/null TARGET", "cd 2>/dev/null TARGET", "cd $'TARGET'", 'cd "TARGET"'])(
"reports the directory a real shell changes to: %s",
async (template) => {
const source = template.replace("TARGET", target)
const targets = [target, fs.realpathSync(target)]
const changed = shells.filter((executable) =>
targets.includes(observe(executable, `${source}\npwd >> "$SCAN_PROBE_LOG"`).at(-1) ?? ""),
)
expect(changed.length, `Fixture never changed directory in any real shell: ${source}`).toBeGreaterThan(0)
for (const executable of changed) {
const parsed = await Effect.runPromiseExit(ShellParse.scanPortable(source, executable, root))
if (Exit.isSuccess(parsed)) expect(parsed.value.directories, `${executable} in: ${source}`).toContain(target)
}
},
)
})
+13 -1
View File
@@ -38,6 +38,11 @@ describe("Bash redirect resource oracle", () => {
`${redirect} FOO=bar git status 3>tail`,
`npm run ${redirect} test`,
]) {
// Dash reads `&>` as `&` and `>`, so words after its target start another command there.
if (redirect.startsWith("&") && !command.endsWith(redirect)) {
expect(ShellScan.scan(command).kind).toBe("opaque")
continue
}
await parity(command)
for (const separator of separators) {
await parity(`printf ok${separator}${command}`)
@@ -80,7 +85,6 @@ describe("Bash redirect resource oracle", () => {
"pwd | cat 2\\>out",
"if true; then printf ok && cat >$(printf path); fi",
"if true; then printf ok && git >out status; else cat >log; fi",
"pwd && cd >out /outside",
"time git status",
"time -p git status",
"coproc git status",
@@ -151,4 +155,12 @@ describe("Bash redirect resource oracle", () => {
{ resource: "FOO=bar >output git status", save: "git status *" },
])
})
test("known gap: redirect before cd operand retains the target directory natively", async () => {
const source = "pwd && cd >out /outside"
const legacy = await Effect.runPromise(ShellParse.scan(source, "/bin/bash", "/workspace"))
const native = await Effect.runPromise(ShellParse.scanPortable(source, "/bin/bash", "/workspace"))
expect(legacy).toEqual({ commands: [{ resource: "pwd", save: "pwd *" }], directories: [] })
expect(native).toEqual({ commands: [{ resource: "pwd", save: "pwd *" }], directories: ["/outside"] })
})
})
+2 -2
View File
@@ -217,6 +217,8 @@ describe("ShellScan", () => {
["CDPATH=/usr # comment\ncd bin; rm victim", ["cd", "rm"]],
["HOME=/etc # comment\ncd; rm victim", ["cd", "rm"]],
["VALUE=$(printf 2); echo $((VALUE + 1))", ["printf", "echo"]],
["MSG='Use ${FOO} here'; some_cmd || exit $?", ["some_cmd", "exit"]],
["f() { local REGEX='[0-9]+${FOO}'; return $?; }", ["local", "return"]],
] as const)("scans assignment-only boundaries without evaluating their effects: %s", (command, names) => {
const result = ShellScan.scan(command)
expect(result.kind).toBe("scanned")
@@ -234,8 +236,6 @@ describe("ShellScan", () => {
"{fd}>/tmp/log touch /tmp/victim",
"time touch /tmp/victim",
"printf '%s' \"$(printf safe ${x%)}; touch /tmp/victim)\"",
"s=abc; x='a[$(touch /tmp/victim)0]'; printf '%s' \"${s:x}\"",
"ref='x[$(touch /tmp/victim)0]'; printf '%s' \"${!ref}\"",
"if true; then echo safe; fi > /tmp/victim",
"if true; then :; 'if' victim; fi",
])("scans Bash lexical forms without interpreting shell values: %s", (command) => {