Compare commits

..
Author SHA1 Message Date
Shoubhit Dash 6eb2042acd Merge remote-tracking branch 'origin/v2' into session-diff
# Conflicts:
#	packages/client/src/effect/api/api.ts
#	packages/core/src/session.ts
#	packages/core/test/git.test.ts
#	packages/protocol/src/groups/session.ts
#	packages/server/src/handlers/session-error.ts
#	packages/server/src/handlers/session.ts
2026-09-08 19:30:14 +05:30
f9bc2233dd fix(app): align desktop agent and model switching (#47286)
Co-authored-by: nexxeln <95541290+nexxeln@users.noreply.github.com>
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-09-08 18:16:08 +08:00
Simon Klee 7487999e06 tabs: add compact session tab rail (#47938) 2026-09-08 12:11:16 +02:00
Simon Klee 2eea36e731 mini: add more minimal output presets. (#47931) 2026-09-08 11:53:02 +02:00
Simon Klee 4fef8edbe8 mini: add clear-screen command (#47928) 2026-09-08 11:24:36 +02:00
Simon Klee 50c552f763 tui: add tool filtering option to Markdown exports (#47929) 2026-09-08 11:24:29 +02:00
Luke Parker a3d5923aca fix(session-ui): stop refetching missing shell output (#47926) 2026-09-08 09:12:15 +00:00
Luke Parker ea2c0184ce fix(app): release attachment blobs when no draft references them (#47922) 2026-09-08 09:11:59 +00:00
Luke Parker 09c318094c fix(app): bound terminal snapshot serialization on teardown (#47924) 2026-09-08 09:04:17 +00:00
Luke Parker 22a534a0bb fix(desktop): skip differential updates when the cache is stale (#47925) 2026-09-08 09:03:14 +00:00
opencode-agent[bot]andBrendonovich c3f1bdaf97 fix(app): cap worktree picker height (#47899)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-09-08 14:52:59 +08:00
opencode-agent[bot] 2bf9bec897 chore: update nix node_modules hashes 2026-09-08 06:51:42 +00:00
Brendan Allan 90dd682e66 feat(app): configure initial servers and add QR pairing (#47799) 2026-09-08 14:32:17 +08:00
opencode-agent[bot]andBrendonovich 64684b118f fix(app): focus auto-created terminals (#47890)
Co-authored-by: Brendonovich <14191578+Brendonovich@users.noreply.github.com>
2026-09-08 14:31:06 +08:00
Dax cab8e39ad5 fix(app): use HTTP-safe attachment and mutation IDs (#47887) 2026-09-08 05:07:37 +00:00
Aiden Cline 5165d6008c fix(ai): default newer Claude models to drop invalid thinking (#47884) 2026-09-07 23:46:10 -05:00
Aiden Cline 4d74854e8c trim redundant opencode instruction (#47878) 2026-09-07 23:16:42 -05:00
Dax Raad 50e17b7f95 fix(desktop): use OpenCode-hosted update releases 2026-09-08 00:04:10 -04:00
Aiden Cline e8177238f6 feat(core): support native Snowflake Cortex authentication (#47156) 2026-09-07 22:50:50 -05:00
Aiden Cline b3f36c0967 feat(ai): add Z.AI language models (#47866) 2026-09-07 21:55:33 -05:00
Kit Langton 1f77408ff2 feat(tui): navigate projects and worktrees
Add project and worktree navigation with restored search and selection, workspace-preserving targets, and optional worktree naming. Keep creation in the Ctrl+N footer and defer filesystem browsing.
2026-09-07 22:35:52 -04:00
Aiden Cline a912a6ee4f fix(core): clarify shell background parameter guidance (#47865) 2026-09-07 20:16:18 -05:00
Aiden Cline 2ac698d65a feat(ai): add Moonshot provider (#47851) 2026-09-07 19:50:55 -05:00
opencode-agent[bot] c1c6ab593d chore: update nix node_modules hashes 2026-09-07 23:40:48 +00:00
Dax Raad d1d1c6f890 feat(release): publish package binaries through Cloudflare
Move files deployments to Wrangler, make CLI and desktop own their publishing destinations, add direct-download update metadata and desktop feeds, and refresh installation docs.
2026-09-07 19:17:29 -04:00
opencode-agent[bot] 0ab661a9cc chore: update nix node_modules hashes 2026-09-07 22:53:49 +00:00
Dax Raad a55dc8c84a feat(services): organize hosted services and add public files 2026-09-07 18:34:34 -04:00
opencode-agent[bot] be41bc4e7d fix(app): keep tab progress visible on hover (#47835) 2026-09-07 22:23:02 +00:00
Dax 567f8b9743 feat(updates): serve updates under opencode.ai/update (#47858) 2026-09-07 18:18:32 -04:00
Dax 6263a35b3f fix(cli): install only opencode for stable AUR releases (#47857) 2026-09-07 18:04:38 -04:00
Dax 74ca560c75 feat(cli): publish stable releases to opencode-bin on AUR (#47856) 2026-09-07 17:59:39 -04:00
Dax a68d6f904d feat(cli): publish beta releases to AUR (#47855) 2026-09-07 17:52:48 -04:00
opencode-agent[bot] cc8c2f8810 chore: update nix node_modules hashes 2026-09-07 21:41:05 +00:00
Dax Raad ad31bff969 docs: remove internal scope migration checklist 2026-09-07 17:22:10 -04:00
Dax a5312e169b refactor(packages): migrate to the opencode npm scope (#47852) 2026-09-07 17:19:33 -04:00
opencode-agent[bot] 16aca14bc7 chore: update nix node_modules hashes 2026-09-07 21:09:25 +00:00
Dax Raad 4aba093c98 fix(updates): scope minimum checks to the caller channel 2026-09-07 16:51:37 -04:00
Dax Raad a3bbcd5c73 fix(updates): respect the default CLI user agent 2026-09-07 16:49:59 -04:00
Dax Raad c05d07cd73 feat(updates): gate releases on minimum client versions 2026-09-07 16:47:01 -04:00
Aiden Cline ef34ada9fb feat(core): add DigitalOcean OAuth and router discovery (#47137) 2026-09-07 15:41:09 -05:00
Dax Raad e15fb426ec fix(browser): publish plugin under opencode scope 2026-09-07 16:27:53 -04:00
Aiden Cline 72433f2ed8 feat(ai): add Meta provider (#47826) 2026-09-07 15:13:48 -05:00
opencode-agent[bot]andJay b32d8c3e58 chore(app): update GitHub star count (#47844)
Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
2026-09-07 15:53:56 -04:00
Aiden Cline 6af8515f69 feat(ai): add MiniMax provider (#47827) 2026-09-07 14:14:14 -05:00
Filip 5c50edb9bb feat(core): expose session rename tool (#47837) 2026-09-07 18:38:26 +00:00
Shoubhit Dash 54504ab3a5 fix(client): synthesize idle messages live
The solid data layer mirrors every projected marker message from its event so the in-memory transcript matches the server before the next read; do the same for the idle marker on execution succeeded, failed, and non-shutdown interrupted.
2026-09-07 23:57:17 +05:30
Aiden Cline fcddc84225 fix(codemode): render empty tools as () and accept zero args (#47833) 2026-09-07 12:52:48 -05:00
Shoubhit Dash cc5086d127 feat(session): add turn diff route
GET /api/session/:sessionID/diff?messageID&to&context returns FileDiff.Info[] for the turn containing a user message (default: the newest one), or the contiguous range through a later user message's turn. A turn runs from the first prompt after the Session was last idle until its idle marker, so steers belong to the turn they interrupted; Sessions without markers fall back to prompt-to-next-prompt. The diff compares the range's first recorded step snapshot with its last recorded one, or with the working copy only while the Session is actively executing, resolves the snapshot repository from the Location in effect at the range (rejecting ranges that span a move), and defaults to full-file patches like vcs.diff. Shared missingMessage and failedSnapshot handler helpers replace the inlined mappings in the session handlers.
2026-09-07 22:08:19 +05:30
Shoubhit Dash b20482461c feat(session): record idle boundaries as messages
Project an idle message when a busy period ends (execution succeeded, failed, or interrupted for any reason other than shutdown, which resumes the same turn). Every step since the previous marker is one turn, including prompts steered in while the Session was busy, so turns are derivable from session_message alone without persisting events or a separate table. The marker is invisible to the model and to the TUI and web transcripts.
2026-09-07 22:00:36 +05:30
Shoubhit Dash 5b5368fe98 perf(core): batch snapshot tree diffs
Git.tree.diff ran --name-status, --numstat, and a patch once per changed file, sequentially, so a turn or revert touching N files cost 1 + 3N git processes (~50ms per file). Run the three once over the tree pair, split the patch with VcsPatch.chunksByFile, cap patch output at MAX_TOTAL_PATCH_BYTES like VCS diffs (capped files get an empty patch, stats stay exact), keep core.quotepath=false so non-ASCII paths still match their chunk, and pass --no-ext-diff. Snapshot.diff diffs first and filters ignored paths from the result instead of listing changed files twice and passing every path as a pathspec.
2026-09-07 21:53:19 +05:30
opencode-agent[bot]andrekram1-node 582a2108ce fix(tui): finish reasoning rows on end event (#47813)
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
2026-09-07 11:12:33 -05:00
OpeOginniandAiden Cline 9c65a69937 fix(core): support granular webfetch permissions (#46611)
Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com>
2026-09-07 11:03:21 -05:00
Aiden Cline 1d391908f4 feat(core): identify to MCP authorization servers with a client metadata document (#47743) 2026-09-07 10:54:29 -05:00
Filip 596dca4dee feat(cli): add session list and delete commands (#47812) 2026-09-07 17:43:56 +02:00
Shoubhit Dash 1827832775 fix(core): retry transient provider compaction failures (#47806) 2026-09-07 21:03:43 +05:30
Shoubhit Dash 898692af26 feat(core): schedule provider compaction automatically (#47324) 2026-09-07 19:27:11 +05:30
Kit Langton 5c3f2ddf8c refactor(core): unify filesystem access policy (#47630) 2026-09-07 09:26:55 -04:00
Shoubhit Dash 1382cebe10 feat(core): support explicit provider compaction (#47323) 2026-09-07 18:20:35 +05:30
Shoubhit Dash 0732cdd8e1 feat(core): persist provider compaction context (#47322) 2026-09-07 18:16:16 +05:30
1861 changed files with 23749 additions and 8650 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
"@opencode-ai/core": patch
"@opencode/core": patch
---
Correct directory page headings when the read offset is zero.
+34
View File
@@ -0,0 +1,34 @@
name: deploy-files
on:
push:
branches:
- dev
- v2
workflow_dispatch:
concurrency:
group: deploy-files-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: read
jobs:
deploy:
if: github.repository == 'anomalyco/opencode' && (github.ref_name == 'dev' || github.ref_name == 'v2')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
- uses: ./.github/actions/setup-bun
- name: Typecheck
working-directory: services/files
run: bun typecheck
- name: Deploy
working-directory: services/files
run: bun run deploy --env ${{ github.ref_name == 'v2' && 'production' || 'dev' }}
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+2 -2
View File
@@ -24,13 +24,13 @@ jobs:
- uses: ./.github/actions/setup-bun
- name: Build
working-directory: packages/www
working-directory: services/www
run: bun run build
env:
CLOUDFLARE_ENV: ${{ github.ref_name == 'v2' && 'production' || 'dev' }}
- name: Deploy
working-directory: packages/www
working-directory: services/www
run: bun run deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+1
View File
@@ -11,6 +11,7 @@ on:
- "bun.lock"
- "package.json"
- "packages/*/package.json"
- "services/*/package.json"
- "flake.lock"
- "nix/node_modules.nix"
- "nix/scripts/**"
+4 -14
View File
@@ -48,7 +48,7 @@ jobs:
- name: Deploy update service
if: github.ref_name == 'v2'
working-directory: packages/updates
working-directory: services/updates
run: bun run deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
@@ -670,19 +670,6 @@ jobs:
git config --global user.name "opencode"
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts || true
- name: Upload desktop release assets
if: needs.version.outputs.release
env:
GH_TOKEN: ${{ steps.committer.outputs.token }}
run: |
shopt -s nullglob
files=(/tmp/desktop/*.{exe,blockmap,dmg,zip,AppImage,deb,rpm} /tmp/desktop/*.app.tar.gz)
if (( ${#files[@]} == 0 )); then
echo "No desktop release assets found"
exit 1
fi
gh release upload "v${{ needs.version.outputs.version }}" "${files[@]}" --clobber --repo "${{ needs.version.outputs.repo }}"
- run: ./script/publish.ts
env:
OPENCODE_VERSION: ${{ needs.version.outputs.version }}
@@ -694,3 +681,6 @@ jobs:
LATEST_YML_DIR: /tmp/latest-yml
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
OPENCODE_DESKTOP_DIST: /tmp/desktop
CLOUDFLARE_ACCOUNT_ID: 15d29c8639fd3733b1b5486a2acfd968
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+4 -4
View File
@@ -49,7 +49,7 @@ jobs:
echo "app=true" >> "$GITHUB_OUTPUT"
exit 0
fi
bun x turbo@2.10.2 ls --affected --filter=@opencode-ai/app --output=json > affected.json
bun x turbo@2.10.2 ls --affected --filter=@opencode/app --output=json > affected.json
bun -e 'const result = await Bun.file("affected.json").json(); console.log(`app=${result.packages.count > 0}`)' >> "$GITHUB_OUTPUT"
unit:
@@ -132,10 +132,10 @@ jobs:
timeout-minutes: 15
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
bun turbo verify:package --filter=@opencode-ai/sdk
bun turbo verify:package --filter=@opencode/sdk
exit 0
fi
bun turbo verify:package --affected --filter=@opencode-ai/sdk
bun turbo verify:package --affected --filter=@opencode/sdk
env:
TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }}
TURBO_SCM_HEAD: ${{ github.sha }}
@@ -173,7 +173,7 @@ jobs:
- name: Check generated documentation
if: runner.os == 'Linux'
working-directory: packages/www
working-directory: services/www
run: bun run check:generated
e2e:
+1 -1
View File
@@ -1,5 +1,5 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
import { tool } from "@opencode/plugin"
async function githubFetch(endpoint: string, options: RequestInit = {}) {
const response = await fetch(`https://api.github.com${endpoint}`, {
...options,
+1 -1
View File
@@ -1,5 +1,5 @@
/// <reference path="../env.d.ts" />
import { tool } from "@opencode-ai/plugin"
import { tool } from "@opencode/plugin"
const TEAM = {
tui: ["kommander", "simonklee"],
+2 -2
View File
@@ -84,9 +84,9 @@ const { a, b } = obj
### Imports
- Never alias imports. Do not use `import { foo as bar } from "..."` or renamed imports like `resolve as pathResolve`.
- Never use type-position `import("...")` references such as `Schema.declare<import("@opencode-ai/plugin/effect/plugin").Plugin["effect"]>`. Only when two imports genuinely collide on a name and no other option exists, an aliased type import (`import type { Plugin as PluginDefinition } from "..."`) is permitted as a last resort — still strongly preferred not to.
- Never use type-position `import("...")` references such as `Schema.declare<import("@opencode/plugin/effect/plugin").Plugin["effect"]>`. Only when two imports genuinely collide on a name and no other option exists, an aliased type import (`import type { Plugin as PluginDefinition } from "..."`) is permitted as a last resort — still strongly preferred not to.
- Never use star imports. Do not use `import * as Foo from "..."` or `import type * as Foo from "..."`.
- If a namespace-style value is needed, import the module's own exported namespace by name, for example `import { Project } from "@opencode-ai/core/project"`, then reference `Project.ID`.
- If a namespace-style value is needed, import the module's own exported namespace by name, for example `import { Project } from "@opencode/core/project"`, then reference `Project.ID`.
- Prefer dynamic imports for heavy modules that are only needed in selected code paths, especially in startup-sensitive entrypoints. Destructure dynamic import bindings near the top of the narrowest scope that needs them so they read like normal imports. Avoid inline chains such as `await import("./module").then((mod) => mod.value())` or `(await import("./module")).value()`. Keep branch-specific imports inside the branch that needs them to preserve lazy loading.
### Variables
+782 -629
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -2,7 +2,7 @@
exact = true
# Only install newly resolved package versions published at least 3 days ago.
minimumReleaseAge = 259200
minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@brendonovich/vite-plugin-opencode", "@opencode-ai/sdk", "@opencode-ai/pty", "@opencode-ai/pty-darwin-arm64", "@opencode-ai/pty-darwin-x64", "@opencode-ai/pty-linux-arm64-gnu", "@opencode-ai/pty-linux-arm64-musl", "@opencode-ai/pty-linux-x64-gnu", "@opencode-ai/pty-linux-x64-musl", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron", "electron-builder", "electron-publish", "blume", "mermaid"]
minimumReleaseAgeExcludes = ["@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@brendonovich/vite-plugin-opencode", "@opencode/sdk", "@opencode-ai/pty", "@opencode-ai/pty-darwin-arm64", "@opencode-ai/pty-darwin-x64", "@opencode-ai/pty-linux-arm64-gnu", "@opencode-ai/pty-linux-arm64-musl", "@opencode-ai/pty-linux-x64-gnu", "@opencode-ai/pty-linux-x64-musl", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron", "electron-builder", "electron-publish", "blume", "mermaid"]
[test]
root = "./do-not-run-tests-from-root"
+1
View File
@@ -6,6 +6,7 @@ export function createWebApp(domain: string) {
$app.stage === "beta"
? {
OPENCODE_CHANNEL: "beta",
VITE_OPENCODE_SERVER_MODE: "none",
VITE_SENTRY_ENVIRONMENT: "beta",
}
: undefined,
+12 -4
View File
@@ -165,22 +165,30 @@ else
exit 1
fi
package_scope="@opencode"
if [ -z "$requested_version" ]; then
metadata=$(curl -fsSL https://registry.npmjs.org/@opencode-ai%2fcli/beta || true)
metadata=$(curl -fsSL https://opencode.ai/update/api/beta/cli/npm || true)
specific_version=$(echo "$metadata" | sed -n 's/.*"version":"\([^"]*\)".*/\1/p')
package=$(echo "$metadata" | sed -n 's/.*"package":"\([^"]*\)".*/\1/p')
if [ -z "$specific_version" ]; then
if [ -z "$specific_version" ] || [ -z "$package" ]; then
echo -e "${RED}Failed to fetch version information${NC}"
exit 1
fi
package_scope="${package%/cli}"
else
# Strip leading 'v' if present
requested_version="${requested_version#v}"
specific_version=$requested_version
fi
package_name="@opencode-ai/cli-$target"
http_status=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/@opencode-ai%2fcli-$target/$specific_version" || true)
package_name="$package_scope/cli-$target"
http_status=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/$package_scope%2fcli-$target/$specific_version" || true)
# Older clients install the minimum release before they can migrate package names.
if [ "$http_status" = "404" ] && [ -n "$requested_version" ]; then
package_name="@opencode-ai/cli-$target"
http_status=$(curl -s -o /dev/null -w "%{http_code}" "https://registry.npmjs.org/@opencode-ai%2fcli-$target/$specific_version" || true)
fi
if [ "$http_status" = "404" ]; then
echo -e "${RED}Error: Version ${specific_version} is not available for $target${NC}"
echo -e "${MUTED}Available versions: https://www.npmjs.com/package/$package_name?activeTab=versions${NC}"
+1 -1
View File
@@ -88,7 +88,7 @@ stdenv.mkDerivation (finalAttrs: {
cd packages/desktop
export OPENCODE_CLI_DIST="$TMPDIR/desktop-cli"
cli_package=$(bun -e 'import { getCurrentCli } from "./scripts/utils.ts"; console.log(getCurrentCli().package.replace("@opencode-ai/", ""))')
cli_package=$(bun -e 'import { getCurrentCli } from "./scripts/utils.ts"; console.log(getCurrentCli().package.replace("@opencode/", ""))')
mkdir -p "$OPENCODE_CLI_DIST/$cli_package/bin"
cp ${lib.getExe opencode} "$OPENCODE_CLI_DIST/$cli_package/bin/opencode2"
+4 -4
View File
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-7NBjLAaZRbirLuBJdQO9iwlonqUFKOkU8u6rh/e8Ij0=",
"aarch64-linux": "sha256-877mqEw+JGTTftYSWvHOsKnFRQ0B5umxY5xW31Rs+ms=",
"aarch64-darwin": "sha256-eaWQZfyQMefy5kn+Q9dAzOnXcwjwx7EEtDuzpocgHOQ=",
"x86_64-darwin": "sha256-mg+Sr8h7d2dmrnOfjiX/ktmA3wuBg/iGZv9ooFvlERc="
"x86_64-linux": "sha256-EKhY3iZDrbNrBhntWpSdtLcmNLte6yVBxpIrCxr1uNM=",
"aarch64-linux": "sha256-0OjDGZHgcnnk6IxkfK6ogeeqsGTqY/dcaZ/XzT23sgA=",
"aarch64-darwin": "sha256-Zk51gnOicaLtPuqCYfgARhm2TjL222w1Y0Em288o0YY=",
"x86_64-darwin": "sha256-hvDZ9zCV6zOSx6i7JZ1kVUMht+JI/jc8/y+aYrNHQ1E="
}
}
+2 -1
View File
@@ -27,11 +27,12 @@ stdenvNoCC.mkDerivation {
fileset = lib.fileset.intersection (lib.fileset.fromSource (lib.sources.cleanSource ../.)) (
lib.fileset.unions [
../packages
../services
../bun.lock
../package.json
../patches
../install # required by desktop build (cli.rs include_str!)
../.github/TEAM_MEMBERS # required by @opencode-ai/script
../.github/TEAM_MEMBERS # required by @opencode/script
]
);
};
+4 -3
View File
@@ -13,7 +13,7 @@
"dev:web": "bun --cwd packages/app dev",
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
"dev:www": "bun run --cwd packages/www dev",
"dev:www": "bun run --cwd services/www dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"bench:devex": "bun run --cwd packages/app test:bench:devex",
"lint": "oxlint",
@@ -34,6 +34,7 @@
"workspaces": {
"packages": [
"packages/*",
"services/*",
"packages/console/*",
"packages/stats/*"
],
@@ -128,8 +129,8 @@
},
"dependencies": {
"@aws-sdk/client-s3": "3.933.0",
"@opencode-ai/plugin": "workspace:*",
"@opencode-ai/script": "workspace:*",
"@opencode/plugin": "workspace:*",
"@opencode/script": "workspace:*",
"heap-snapshot-toolkit": "1.1.3",
"typescript": "catalog:"
},
+3 -2
View File
@@ -13,6 +13,7 @@
Per-type constructors live on the type, not as top-level re-exports. Use `Message.system(...)`, `Message.user(...)`, `Message.assistant(...)`, `Message.tool(...)`, `LanguageModel.make(...)`, `ToolDefinition.make(...)`, `ToolCallPart.make(...)`, `ToolResultPart.make(...)`, `ToolChoice.make(...)`, `ToolChoice.named(...)`, `SystemPart.make(...)`, and `GenerationOptions.make(...)` directly. The top-level `LLM` namespace is reserved for request-shaped call APIs: `LLM.request`, `LLM.generate`, `LLM.stream`, and `LLM.generateObject`. Use `LLMRequest.update(...)` when deriving canonical request data; do not add a duplicate `LLM.updateRequest(...)` path. Two ways to construct the same thing is one too many.
- Keep provider-defined string enums forward-compatible. Expose known values for autocomplete while accepting future values with `Known | (string & {})`; use `Schema.String` at runtime unless rejecting unknown values is required for correctness.
- Order reasoning-effort values from lowest to highest: `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`. Provider-specific subsets follow the same relative order in types, schemas, option lists, and tests.
## Tests
@@ -121,10 +122,10 @@ Keep provider facades small and explicit:
### Provider Package Entrypoints
Catalog-selected native providers use package-like export paths from `@opencode-ai/ai`. They are internal entrypoints in one npm package, not separately published provider packages. Every entrypoint implements `ProviderPackage.Definition` and exposes `model(modelID, settings)`, where settings are serializable provider configuration plus common `headers`, `body`, and `limits` overlays.
Catalog-selected native providers use package-like export paths from `@opencode/ai`. They are internal entrypoints in one npm package, not separately published provider packages. Every entrypoint implements `ProviderPackage.Definition` and exposes `model(modelID, settings)`, where settings are serializable provider configuration plus common `headers`, `body`, and `limits` overlays.
```ts
import { model } from "@opencode-ai/ai/providers/openai/responses"
import { model } from "@opencode/ai/providers/openai/responses"
const selected = model("gpt-5", {
apiKey,
+271 -33
View File
@@ -1,12 +1,12 @@
# @opencode-ai/ai
# @opencode/ai
Schema-first language model and image-generation APIs built with Effect.
```ts
import { Effect, Layer } from "effect"
import { LLM, LLMClient } from "@opencode-ai/ai"
import { RequestExecutor } from "@opencode-ai/ai/route"
import { OpenAI } from "@opencode-ai/ai/providers"
import { LLM, LLMClient } from "@opencode/ai"
import { RequestExecutor } from "@opencode/ai/route"
import { OpenAI } from "@opencode/ai/providers"
const model = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
@@ -29,13 +29,251 @@ await Effect.runPromise(program.pipe(Effect.provide(llmLayer)))
Run `LLMClient.stream(request)` instead of `generate` when you want incremental `LLMEvent`s. The event stream is provider-neutral — same shape across OpenAI Chat, OpenAI Responses, Anthropic Messages, Gemini, Bedrock Converse, and any OpenAI-compatible deployment.
## Z.AI
`ZAI` uses the standard API. Chat Completions is the default language-model API;
the existing `.image(...)` selector provides image generation.
```ts
import { LLM } from "@opencode/ai"
import { ZAI, ZAICodingPlan } from "@opencode/ai/providers"
const zai = ZAI.configure({ apiKey: process.env.ZAI_API_KEY })
const request = LLM.request({
model: zai.model("glm-5.3"), // also zai.chat("glm-5.3")
prompt: "Explain this design.",
providerOptions: {
reasoningEffort: "high",
thinking: { type: "enabled", clear_thinking: false },
},
})
const coding = ZAICodingPlan.configure({ apiKey: process.env.ZAI_API_KEY })
const messages = LLM.request({
model: coding.messages("glm-5.3"),
prompt: "Explain this design.",
providerOptions: { effort: "high" },
})
```
The products have distinct provider identities and endpoints:
| Provider | Selector | Default base URL |
| ----------------------------------- | --------------------------- | ------------------------------------- |
| `ZAI` (`zai`) | `.model`, `.chat`, `.image` | `https://api.z.ai/api/paas/v4` |
| `ZAICodingPlan` (`zai-coding-plan`) | `.model`, `.chat` | `https://api.z.ai/api/coding/paas/v4` |
| `ZAICodingPlan` | `.messages` | `https://api.z.ai/api/anthropic/v1` |
| `ZAICodingPlan` | `.responses` | `https://api.z.ai/api/v1` |
Both read `ZAI_API_KEY` when `apiKey` is omitted and support an explicit `auth` override.
Coding Plan requires an active subscription. `baseURL` overrides the selected API's
complete base, including its version prefix. Language-model routes use HTTP/SSE.
Options retain the selected API's native semantics:
- Chat `reasoningEffort` lowers to `reasoning_effort`; Responses lowers it to `reasoning.effort`.
Messages `effort` lowers to `output_config.effort`. Omission preserves provider defaults.
- Chat `thinking` passes `type` and `clear_thinking` through unchanged. Set
`clear_thinking: false` and replay complete `response.message` values to preserve reasoning
across user messages and tool loops. The standard API defaults to clearing historical thinking;
Coding Plan documents preservation by default.
- Messages accepts `thinking: { type: "enabled" | "adaptive" | "disabled" }` without requiring
an Anthropic token budget. Coding Plan documents a disabled toggle as low-effort thinking
for GLM-5.3, with explicit effort taking precedence.
- Chat also offers `toolStream`, `doSample`, `responseFormat`, `requestID`, and `userID`.
Tool-argument streaming is enabled when tools are present on GLM-4.6/4.7/5.x;
`toolStream: false` explicitly disables it. Older model families omit the opt-in.
- Effort and thinking values remain forward-compatible strings. Their meaning is model-specific:
GLM-5.3 accepts `low`, `high`, and `max` effort and rejects disabled thinking with HTTP 400;
the direct GLM-5.2 recordings returned reasoning even with `none` and `minimal` effort,
whereas explicit `thinking.type: "disabled"` disabled it on GLM-5.2 and GLM-4.7.
Standard API recordings cover GLM-5.3 efforts and a full preserved-reasoning tool loop with
a subsequent user follow-up, GLM-5.2 efforts, older-model thinking toggles, GLM-4.5 tool calls,
GLM-5.3-Flash image input, and JSON output. Coding Plan has unit coverage for routing,
request options, and reasoning replay; successful live recordings are pending.
Package entrypoints are `@opencode/ai/providers/zai`, `zai/chat`, `zai-coding-plan`,
`zai-coding-plan/chat`, `zai-coding-plan/messages`, and `zai-coding-plan/responses`.
## Moonshot
Moonshot defaults to Chat Completions, with Messages and Responses selectors for Kimi K3:
```ts
import { LLM } from "@opencode/ai"
import { Moonshot } from "@opencode/ai/providers"
const moonshot = Moonshot.configure({ apiKey: process.env.MOONSHOT_API_KEY })
const request = LLM.request({
model: moonshot.model("kimi-k3"), // also moonshot.chat("kimi-k3")
prompt: "Explain the tradeoffs in this design.",
providerOptions: { reasoningEffort: "high" },
})
const messages = LLM.request({
model: moonshot.messages("kimi-k3"),
prompt: "Explain the tradeoffs in this design.",
providerOptions: { effort: "high" },
})
const responses = LLM.request({
model: moonshot.responses("kimi-k3"),
prompt: "Explain the tradeoffs in this design.",
providerOptions: { reasoningEffort: "high" },
})
```
When `apiKey` is omitted, authentication reads `MOONSHOT_API_KEY`, then `MOONSHOTAI_API_KEY`.
Chat and Responses use `https://api.moonshot.ai/v1`; Messages uses
`https://api.moonshot.ai/anthropic/v1`. `baseURL` overrides the selected API's complete base,
including the version prefix, for regional endpoints or gateways. Each endpoint requires its own valid credentials.
All three routes use HTTP/SSE.
Reasoning options stay native to the selected API and model:
| Model/API | Provider options |
| --------------------------- | --------------------------------------------------------------------------------------- |
| K3 Chat / Responses | `reasoningEffort: "low" \| "high" \| "max"`; default is `max` |
| K3 Messages | `effort: "low" \| "high" \| "max"`; default is `max` |
| K2.6 Chat | `thinking: { type: "enabled" \| "disabled", keep?: "all" \| null }`; default is enabled |
| K2.7 Code / high-speed Chat | Omit `thinking` to use always-on, preserved reasoning |
Omitting options preserves the model's defaults. K3 uses effort rather than the K2.x `thinking`
parameter. Known effort values have autocomplete while future strings remain accepted.
For K2.6, `thinking.keep: "all"` enables preservation of reasoning across user messages.
K3 and both K2.7 Code variants always preserve reasoning. Continue with the returned
`response.message` and matching tool results so reasoning content and any Messages signatures are retained.
Leave sampling options such as `temperature` unset to use these models' fixed defaults.
The recorded suite covers all three K3 APIs, default and explicit efforts, K2.6 thinking modes,
both K2.7 Code variants, generated tool loops with a subsequent user follow-up, required/disabled
tool choice, image-byte input, and native structured output through `http.body` overlays.
K3 Chat and Messages accept required and disabled tool choice. Responses supports automatic tool
choice only; explicit `required` and `none` produce a provider `InvalidRequest` error, also covered by recordings.
The provider targets the Moonshot Open Platform; Kimi Code is a separate product and endpoint.
Package entrypoints are `@opencode/ai/providers/moonshot`, `moonshot/chat`, `moonshot/messages`,
and `moonshot/responses`; each exports `model(modelID, settings)`.
## MiniMax
MiniMax defaults to its Messages API and reads `MINIMAX_API_KEY` when `apiKey` is omitted:
```ts
import { Effect, Layer } from "effect"
import { LLM, LLMClient } from "@opencode/ai"
import { MiniMax } from "@opencode/ai/providers"
import { RequestExecutor } from "@opencode/ai/route"
const minimax = MiniMax.configure({ apiKey: process.env.MINIMAX_API_KEY })
const request = LLM.request({
model: minimax.model("MiniMax-M3"), // also minimax.messages("MiniMax-M3")
prompt: "What is 173 multiplied by 219?",
providerOptions: { thinking: { type: "adaptive" } },
generation: { maxTokens: 1536 },
})
const layer = LLMClient.layer.pipe(Layer.provide(RequestExecutor.fetchLayer))
const response = await Effect.runPromise(LLMClient.generate(request).pipe(Effect.provide(layer)))
console.log(response.text)
```
Select `minimax.chat("MiniMax-M3")` or `minimax.responses("MiniMax-M3")` for MiniMax's native Chat Completions
and Responses APIs. The matching package entrypoints are `@opencode/ai/providers/minimax/messages`,
`@opencode/ai/providers/minimax/chat`, and `@opencode/ai/providers/minimax/responses`.
- **Messages:** M3 thinking defaults off. Set `thinking: { type: "adaptive" }` to enable it or
`thinking: { type: "disabled" }` to disable it.
- **Chat:** M3 thinking defaults on and uses the same `thinking` control. The provider enables `reasoning_split`
by default so reasoning is separate from answer text; `reasoningSplit: false` selects native `<think>`-tagged text.
- **Responses:** M3 reasoning defaults off. `reasoningEffort: "none"` disables it; `"minimal"`, `"low"`,
`"medium"`, and `"high"` enable reasoning without changing its depth.
M2.x models always think, even when a disabling option is supplied. For tool continuations, retain the complete
`response.message` in history before adding `Message.tool(...)` results; this preserves reasoning and any signatures.
The default API bases are `https://api.minimax.io/anthropic/v1` for Messages and `https://api.minimax.io/v1` for
Chat and Responses. `configure({ baseURL })` replaces the selected API's base, including its version prefix.
## Meta
Use Meta's direct [Model API](https://dev.meta.ai/docs/overview) with `META_API_KEY`:
```ts
import { Meta } from "@opencode/ai/providers"
const meta = Meta.configure() // or Meta.configure({ apiKey })
const request = LLM.request({
model: meta.responses("muse-spark-1.3"), // meta.model(...) also selects Responses
prompt: "What is 173 multiplied by 219? Reply with the integer.",
providerOptions: { reasoningEffort: "low" },
generation: { maxTokens: 1024 },
})
```
`meta.chat("muse-spark-1.3")` selects Chat Completions; `meta.messages("muse-spark-1.3")` selects
the Anthropic-compatible Messages API. All use `https://api.meta.ai/v1`. The package entrypoints
`@opencode/ai/providers/meta/responses`, `meta/chat`, and `meta/messages` expose `model(modelID, settings)`.
[Muse Spark](https://dev.meta.ai/docs/models) supports `minimal`, `low`, `medium`, `high`, and
`xhigh` reasoning effort; standard-tier 1.3 also supports `max`. Omitting effort uses the model's
default. Muse Spark always reasons and rejects `none`. The output-token budget includes private reasoning.
Responses defaults to `store: false` and `include: ["reasoning.encrypted_content"]`. Preserve
`response.message` along with matching `Message.tool(...)` results in subsequent requests to replay
reasoning through tool loops. Optional `reasoningSummary: "auto"` requests a readable summary.
For server-managed history, override `store: true, include: []` and send the response ID through
`http: { body: { previous_response_id: responseID } }` with only the new input.
Chat Completions redacts private reasoning and cannot carry it between calls.
Responses and Chat support only `toolChoice: "auto"` (the default). Messages also accepts `"none"`;
its documented forced `"any"` choice currently returns HTTP 400. Messages defaults to adaptive thinking
with `display: "omitted"`, preserving encrypted `redacted_thinking` in `response.message`. Use
`providerOptions: { effort: "low" }` for depth or `thinking: { type: "enabled", budgetTokens: 1024 }`
for budget compatibility (with `generation.maxTokens > 1024`).
Add `tools: [Meta.webSearch()]` to a Spark Responses or Messages request for hosted web search.
Responses exposes hosted results and URL citations in text-part `providerMetadata.meta.annotations`.
To include search result lists, set `include: ["reasoning.encrypted_content", "web_search_call.results"]`.
Messages exposes hosted search calls; the recorded Messages API stream does not supply structured
citations or separate result blocks. Retain `response.message` for either API's continuation.
Use `Image.generate` for one-off generation or editing:
```ts
import { Image, ImageInput } from "@opencode/ai"
const generation = Image.generate({
model: meta.image("muse-image-1.0"),
prompt: "A flat black square on a white background.",
options: { n: 1, reasoningStrength: "low" },
})
const edit = Image.generate({
model: meta.image("muse-image-1.0"),
prompt: "Make the square purple.",
images: [ImageInput.bytes(imageBytes, "image/webp")],
options: { outputFormat: "png", reasoningStrength: "low" },
})
```
The default image format is WEBP; `outputFormat` also accepts PNG/JPEG and `responseFormat: "url"`
returns a signed URL. `size` is an aspect-ratio hint. For conversational images, select
`meta.responses("muse-image-1.0")` with `tools: [Meta.imageGeneration({ reasoningStrength: "low" })]`.
Generated images are provider-executed tool results with file content. Retain `response.message` to
replay the signed image handle on the next request. Muse Image accepts only the `image_generation` tool.
Meta Responses is explicitly HTTP/SSE-only and does not use WebSockets, even when a caller supplies
`StreamOptions.webSocket`. The public `/v1/responses` endpoint rejects WebSocket upgrades with HTTP 405 (`Allow: POST`).
## Image generation
Use `Image.generate` with an image model for direct asset generation:
```ts
import { Image, ImageInput } from "@opencode-ai/ai"
import { OpenAI } from "@opencode-ai/ai/providers"
import { Image, ImageInput } from "@opencode/ai"
import { OpenAI } from "@opencode/ai/providers"
const program = Effect.gen(function* () {
const response = yield* Image.generate({
@@ -131,7 +369,7 @@ yield *
Google's current Gemini image models use the same direct API:
```ts
import { Google } from "@opencode-ai/ai/providers"
import { Google } from "@opencode/ai/providers"
const googleProgram = Effect.gen(function* () {
const response = yield* Image.generate({
@@ -207,12 +445,12 @@ The hosted result is represented as a provider-executed tool call and tool resul
## Testing
Use the deterministic test client from `@opencode-ai/ai/testing` to script provider-neutral responses and inspect
Use the deterministic test client from `@opencode/ai/testing` to script provider-neutral responses and inspect
the requests sent by code under test:
```ts
import { Effect } from "effect"
import { TestLLM } from "@opencode-ai/ai/testing"
import { TestLLM } from "@opencode/ai/testing"
const programWithTestClient = Effect.gen(function* () {
const test = yield* TestLLM.Test
@@ -323,8 +561,8 @@ This capability describes protocol implementation, **not universal availability
Inside an `Effect.gen`, enable OpenAI compaction with typed provider options:
```ts
import { LLM, LLMClient, LLMRequest, Message } from "@opencode-ai/ai"
import { OpenAI } from "@opencode-ai/ai/providers"
import { LLM, LLMClient, LLMRequest, Message } from "@opencode/ai"
import { OpenAI } from "@opencode/ai/providers"
const request = LLM.request({
model: OpenAI.configure({ apiKey }).responses("gpt-5.3-codex"),
@@ -344,7 +582,7 @@ const next = LLMRequest.update(request, {
A compaction part has `provider` and exactly one representation: `encrypted` for Responses, or `text` for Anthropic. Responses also preserves the optional checkpoint `id`. These fields survive message serialization without becoming visible assistant text. Sending a checkpoint to another provider or an incompatible API fails rather than silently losing context.
```ts
import { CompactionPart, ProviderID } from "@opencode-ai/ai"
import { CompactionPart, ProviderID } from "@opencode/ai"
CompactionPart.make({ provider: ProviderID.make("openai"), id: "cmp_123", encrypted: "..." })
CompactionPart.make({ provider: ProviderID.make("anthropic"), text: "Summary of the conversation..." })
@@ -450,7 +688,7 @@ Normalized cache usage is read back into `response.usage.cacheReadInputTokens` a
Provider facades configure endpoint/auth/deployment details first, then expose model selectors that take only a model or deployment id. The selected model carries the executable route value used at runtime.
```ts
import { OpenAI, CloudflareAIGateway } from "@opencode-ai/ai/providers"
import { OpenAI, CloudflareAIGateway } from "@opencode/ai/providers"
const openai = OpenAI.configure({ apiKey: process.env.OPENAI_API_KEY }).responses("gpt-4o-mini")
const gateway = CloudflareAIGateway.configure({
@@ -464,7 +702,7 @@ Included LLM providers: OpenAI, Anthropic, Google (Gemini), Google Vertex, Amazo
Each named provider owns its module, endpoint, authentication, and route setup. Providers with the same wire format compose the shared protocol directly:
```ts
import { DeepSeek, Fireworks } from "@opencode-ai/ai/providers"
import { DeepSeek, Fireworks } from "@opencode/ai/providers"
const deepseek = DeepSeek.configure({ apiKey }).model("deepseek-chat")
const fireworks = Fireworks.configure({ apiKey }).model("accounts/fireworks/models/my-model")
@@ -474,10 +712,10 @@ The former `OpenAICompatible.baseten`, `.cerebras`, `.deepinfra`, `.deepseek`, `
### Provider entrypoints
Provider modules are available through dedicated exports from `@opencode-ai/ai`. Each LLM entrypoint exports `model(modelID, settings)`, where `settings` contains provider configuration plus common `headers` and `body` overlays.
Provider modules are available through dedicated exports from `@opencode/ai`. Each LLM entrypoint exports `model(modelID, settings)`, where `settings` contains provider configuration plus common `headers` and `body` overlays.
```ts
import { model } from "@opencode-ai/ai/providers/openai/responses"
import { model } from "@opencode/ai/providers/openai/responses"
const selected = model("gpt-5", {
apiKey: process.env.OPENAI_API_KEY,
@@ -487,14 +725,14 @@ const selected = model("gpt-5", {
APIs have separate entrypoints:
- `@opencode-ai/ai/providers/openai/chat`
- `@opencode-ai/ai/providers/openai/responses`
- `@opencode-ai/ai/providers/openai-compatible/responses`
- `@opencode-ai/ai/providers/anthropic-compatible`
- `@opencode-ai/ai/providers/google-vertex/gemini`
- `@opencode-ai/ai/providers/google-vertex/chat`
- `@opencode-ai/ai/providers/google-vertex/responses`
- `@opencode-ai/ai/providers/google-vertex/messages`
- `@opencode/ai/providers/openai/chat`
- `@opencode/ai/providers/openai/responses`
- `@opencode/ai/providers/openai-compatible/responses`
- `@opencode/ai/providers/anthropic-compatible`
- `@opencode/ai/providers/google-vertex/gemini`
- `@opencode/ai/providers/google-vertex/chat`
- `@opencode/ai/providers/google-vertex/responses`
- `@opencode/ai/providers/google-vertex/messages`
OpenAI Responses has one semantic route and uses HTTP by default. Advanced callers may supply a per-call WebSocket channel executor through `StreamOptions`; transport policy does not change provider settings, model identity, or route identity. The provider-neutral Open Responses implementation owns the reusable WebSocket request and event contract, while each provider opts in with its own handshake and connection policy. Azure follows the same Chat/Responses split at `providers/azure/chat` and `providers/azure/responses`. Generic OpenAI-compatible Chat remains at `providers/openai-compatible`; the Responses adapter at `providers/openai-compatible/responses` uses the provider-neutral Open Responses protocol. OpenAI Responses extends that baseline with OpenAI tools, event variants, metadata, and defaults. Generic Anthropic Messages-compatible providers use `providers/anthropic-compatible`, which the named Anthropic provider composes. Google Gemini and Amazon Bedrock expose their single native API through their existing provider paths.
@@ -503,36 +741,36 @@ Vertex Gemini, Vertex Chat, Vertex Responses, and Vertex Messages are separate A
Tuned Vertex Gemini deployments use model ids shaped like `endpoints/1234567890` and require OAuth or ADC; Vertex express-mode API keys support publisher models only.
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/gemini"
import { model } from "@opencode/ai/providers/google-vertex/gemini"
model("gemini-3.5-flash", { project: "my-project", location: "global" })
```
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/chat"
import { model } from "@opencode/ai/providers/google-vertex/chat"
model("deepseek-ai/deepseek-v3.2-maas", { project: "my-project", location: "global" })
```
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/responses"
import { model } from "@opencode/ai/providers/google-vertex/responses"
model("xai/grok-4.20-reasoning", { project: "my-project", location: "global" })
```
```ts
import { model } from "@opencode-ai/ai/providers/google-vertex/messages"
import { model } from "@opencode/ai/providers/google-vertex/messages"
model("claude-sonnet-4-6", { project: "my-project", location: "global" })
```
Additional provider entrypoints include:
- `@opencode-ai/ai/providers/baseten`
- `@opencode-ai/ai/providers/deepseek`
- `@opencode-ai/ai/providers/fireworks`
- `@opencode-ai/ai/providers/cloudflare-ai-gateway`
- `@opencode-ai/ai/providers/cloudflare-workers-ai`
- `@opencode/ai/providers/baseten`
- `@opencode/ai/providers/deepseek`
- `@opencode/ai/providers/fireworks`
- `@opencode/ai/providers/cloudflare-ai-gateway`
- `@opencode/ai/providers/cloudflare-workers-ai`
## Provider options & HTTP overlays
+3 -3
View File
@@ -1,7 +1,7 @@
import { Config, Effect, Formatter, Layer, Schema, Stream } from "effect"
import { LLM, LLMClient, LLMRequest, Message, ProviderID, Tool, ToolRuntime } from "@opencode-ai/ai"
import { Route, Auth, Endpoint, Framing, Protocol, RequestExecutor } from "@opencode-ai/ai/route"
import { OpenAI } from "@opencode-ai/ai/providers"
import { LLM, LLMClient, LLMRequest, Message, ProviderID, Tool, ToolRuntime } from "@opencode/ai"
import { Route, Auth, Endpoint, Framing, Protocol, RequestExecutor } from "@opencode/ai/route"
import { OpenAI } from "@opencode/ai/providers"
/**
* A runnable walkthrough of the LLM package use-site API.
+3 -3
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"version": "1.17.20",
"name": "@opencode-ai/ai",
"name": "@opencode/ai",
"type": "module",
"license": "MIT",
"scripts": {
@@ -21,7 +21,7 @@
"devDependencies": {
"@clack/prompts": "1.0.0-alpha.1",
"@effect/platform-node": "catalog:",
"@opencode-ai/http-recorder": "workspace:*",
"@opencode/http-recorder": "workspace:*",
"@tsconfig/bun": "catalog:",
"@types/bun": "catalog:",
"@typescript/native-preview": "catalog:",
@@ -31,7 +31,7 @@
"@aws-sdk/credential-providers": "3.1057.0",
"@smithy/eventstream-codec": "4.2.14",
"@smithy/util-utf8": "4.2.2",
"@opencode-ai/schema": "workspace:*",
"@opencode/schema": "workspace:*",
"aws4fetch": "1.0.20",
"effect": "catalog:",
"google-auth-library": "10.5.0"
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bun
import { Script } from "@opencode-ai/script"
import { Script } from "@opencode/script"
import { $ } from "bun"
import { fileURLToPath } from "url"
+74 -20
View File
@@ -1,6 +1,6 @@
import { Buffer } from "node:buffer"
import { Effect, Option, Schema } from "effect"
import { Tool } from "@opencode-ai/schema/tool"
import { Tool } from "@opencode/schema/tool"
import { Route } from "../route/client.js"
import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
@@ -50,15 +50,24 @@ const SSE_EVENTS = new Set([
])
export const framing = Framing.sseEvents(SSE_EVENTS)
export type ThinkingBlockBinding = {
readonly prefix_mismatch_behavior?: "error" | "drop_block" | (string & {})
}
export type ThinkingInput =
| {
readonly type: "adaptive"
readonly display?: "summarized" | "omitted"
readonly block_binding?: ThinkingBlockBinding
}
| {
readonly type: "disabled"
}
| ({ readonly type: "enabled"; readonly display?: "summarized" | "omitted" } & (
| ({
readonly type: "enabled"
readonly display?: "summarized" | "omitted"
readonly block_binding?: ThinkingBlockBinding
} & (
| { readonly budgetTokens: number; readonly budget_tokens?: number }
| { readonly budgetTokens?: number; readonly budget_tokens: number }
))
@@ -301,20 +310,27 @@ const AnthropicToolChoice = Schema.Union([
}),
])
const AnthropicThinkingBlockBinding = Schema.Struct({
prefix_mismatch_behavior: Schema.optional(Schema.String),
})
const AnthropicThinking = Schema.Union([
Schema.Struct({
type: Schema.tag("enabled"),
budget_tokens: Schema.Number,
display: Schema.optional(Schema.Literals(["summarized", "omitted"])),
block_binding: Schema.optional(AnthropicThinkingBlockBinding),
}),
Schema.Struct({
type: Schema.tag("adaptive"),
display: Schema.optional(Schema.Literals(["summarized", "omitted"])),
block_binding: Schema.optional(AnthropicThinkingBlockBinding),
}),
Schema.Struct({
type: Schema.tag("disabled"),
}),
])
type AnthropicThinking = typeof AnthropicThinking.Type
// SDK OutputConfig:2684 {effort?: "low"|"medium"|"high"|"xhigh"|"max"|null, format?: JSONOutputFormat:2399}
const AnthropicJsonOutputFormat = Schema.Struct({
@@ -1025,8 +1041,9 @@ const resolveOptions = Effect.fn("AnthropicMessages.resolveOptions")(function* (
...(outputConfigEffort === undefined ? {} : { effort: outputConfigEffort }),
...(outputConfigFormat === undefined ? {} : { format: outputConfigFormat }),
}
const thinking = yield* resolveThinking(input?.thinking)
return {
thinking: yield* resolveThinking(input?.thinking),
thinking: applyThinkingBindingDefault(request.model, thinking),
effort: outputConfigEffort,
output_config,
service_tier,
@@ -1037,15 +1054,41 @@ const resolveOptions = Effect.fn("AnthropicMessages.resolveOptions")(function* (
}
})
const supportsThinkingBlockBinding = (model: LLMRequest["model"]) => {
const override = model.compatibility?.supportsThinkingBlockBinding
if (override !== undefined) return override
// Accept gateway namespaces and Vertex suffixes without treating a snapshot date as a minor version.
const version = /(?:^|[./])claude-[a-z]+-(?<major>\d+)(?:[.-](?<minor>\d{1,2}))?(?:$|[-:@])/i.exec(model.id)?.groups
if (!version) return false
const major = Number(version.major)
const minor = Number(version.minor ?? 0)
return major > 5 || (major === 5 && minor >= 1)
}
const applyThinkingBindingDefault = (model: LLMRequest["model"], thinking: AnthropicThinking | undefined) => {
if (thinking?.type === "disabled") return thinking
if (!supportsThinkingBlockBinding(model)) return thinking
return {
...(thinking ?? { type: "adaptive" as const }),
block_binding: {
prefix_mismatch_behavior: "drop_block",
...thinking?.block_binding,
},
}
}
const resolveThinking = Effect.fn("AnthropicMessages.resolveThinking")(function* (input: unknown) {
if (!ProviderShared.isRecord(input)) return undefined
if (input.type === "disabled") return { type: "disabled" as const }
if (input.type !== "adaptive" && input.type !== "enabled") return undefined
const block_binding = yield* ProviderShared.validateWith(
Schema.decodeUnknownEffect(Schema.UndefinedOr(AnthropicThinkingBlockBinding)),
)(input.block_binding)
const display =
input.display === "summarized" || input.display === "omitted"
? (input.display as "summarized" | "omitted")
: undefined
if (input.type === "adaptive") return { type: "adaptive" as const, ...(display === undefined ? {} : { display }) }
if (input.type === "disabled") return { type: "disabled" as const }
if (input.type !== "enabled") return undefined
if (input.type === "adaptive") return { type: "adaptive" as const, display, block_binding }
const budget =
typeof input.budgetTokens === "number"
? input.budgetTokens
@@ -1054,7 +1097,7 @@ const resolveThinking = Effect.fn("AnthropicMessages.resolveThinking")(function*
: undefined
if (budget === undefined)
return yield* ProviderShared.invalidRequest("Anthropic thinking provider option requires budgetTokens")
return { type: "enabled" as const, budget_tokens: budget, ...(display === undefined ? {} : { display }) }
return { type: "enabled" as const, budget_tokens: budget, display, block_binding }
})
const fromRequest = Effect.fn("AnthropicMessages.fromRequest")(function* (request: LLMRequest) {
@@ -1636,24 +1679,21 @@ export const protocol = Protocol.make({
},
})
export const transport = <Body extends Pick<AnthropicMessagesBody, "messages" | "context_management">>() => {
export const transport = <
Body extends Pick<AnthropicMessagesBody, "messages" | "context_management" | "thinking">,
>() => {
const http = HttpTransport.httpJson<Body, string>({ framing })
return {
...http,
prepare: (input: Parameters<typeof http.prepare>[0]) => {
if (
!input.body.context_management?.edits.length &&
!input.body.messages.some((message) => message.content.some((block) => block.type === "compaction"))
)
return http.prepare(input)
const requiredBetas = requiredBetaHeaders(input.body)
if (requiredBetas.length === 0) return http.prepare(input)
const headers = Headers.fromInput(input.request.http?.headers)
const betas = new Set(
(headers["anthropic-beta"] ?? "")
.split(",")
.map((item) => item.trim())
.filter(Boolean),
)
betas.add("compact-2026-01-12")
const existingBetas = (headers["anthropic-beta"] ?? "")
.split(",")
.map((item) => item.trim())
.filter(Boolean)
const betas = new Set([...existingBetas, ...requiredBetas])
return http.prepare({
...input,
request: LLMRequest.update(input.request, {
@@ -1667,6 +1707,20 @@ export const transport = <Body extends Pick<AnthropicMessagesBody, "messages" |
}
}
function requiredBetaHeaders(body: Pick<AnthropicMessagesBody, "messages" | "context_management" | "thinking">) {
const betas: string[] = []
const requestsCompaction = (body.context_management?.edits.length ?? 0) > 0
const replaysCompaction = body.messages.some((message) =>
message.content.some((block) => block.type === "compaction"),
)
if (requestsCompaction || replaysCompaction) betas.push("compact-2026-01-12")
const thinking = body.thinking
if (thinking && thinking.type !== "disabled" && thinking.block_binding)
betas.push("thinking-binding-controls-2026-08-01")
return betas
}
export const route = Route.make({
id: ADAPTER,
provider: "anthropic",
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Option, Schema } from "effect"
import { Tool } from "@opencode-ai/schema/tool"
import { Tool } from "@opencode/schema/tool"
import { Route } from "../route/client.js"
import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
+133
View File
@@ -0,0 +1,133 @@
import { Effect, Encoding, Schema } from "effect"
import { Headers, HttpClientRequest } from "effect/unstable/http"
import { GeneratedImage, ImageModel, ImageResponse, type ImageRequestFor, type ImageRoute } from "../image.js"
import { Auth } from "../route/auth.js"
import { Usage, mergeHttpOptions, mergeJsonRecords, type HttpOptions } from "../schema/index.js"
import { JsonObject, ProviderShared, optionalNull } from "./shared.js"
import { ImageInputs } from "./utils/image-input.js"
type OpenString<Known extends string> = Known | (string & {})
export type ImageOptions = {
readonly n?: number
/** Aspect ratio hint, not an exact output resolution. */
readonly size?: string
readonly outputFormat?: OpenString<"webp" | "png" | "jpeg">
readonly responseFormat?: OpenString<"b64_json" | "url">
readonly reasoningStrength?: OpenString<"low" | "high">
readonly toolEnablement?: {
readonly enable_image_search?: boolean
readonly enable_web_search?: boolean
readonly enable_shell?: boolean
}
readonly [key: string]: unknown
}
const Body = Schema.StructWithRest(
Schema.Struct({
model: Schema.String,
prompt: Schema.String,
images: Schema.optional(Schema.Array(JsonObject)),
n: Schema.optional(Schema.Number),
size: Schema.optional(Schema.String),
output_format: Schema.optional(Schema.String),
response_format: Schema.optional(Schema.String),
reasoning_strength: Schema.optional(Schema.String),
tool_enablement: Schema.optional(Schema.Record(Schema.String, Schema.Boolean)),
}),
[JsonObject],
)
const Response = Schema.Struct({
data: Schema.Array(Schema.Struct({ b64_json: optionalNull(Schema.String), url: optionalNull(Schema.String) })),
output_format: Schema.optional(Schema.String),
usage: Schema.optional(
Schema.Struct({
input_tokens: Schema.optional(Schema.Number),
output_tokens: Schema.optional(Schema.Number),
total_tokens: Schema.optional(Schema.Number),
}),
),
})
export const model = (input: {
readonly id: string
readonly auth: Auth.Definition
readonly baseURL: string
readonly headers?: Record<string, string>
readonly http?: HttpOptions
}) => {
const route: ImageRoute<ImageOptions> = {
id: "meta-images",
generate: Effect.fn("MetaImages.generate")(function* (request: ImageRequestFor<ImageOptions>, execute) {
const http = mergeHttpOptions(request.model.http, request.http)
const images = yield* Effect.forEach(request.images ?? [], (image) => {
if (image.type === "bytes") return Effect.succeed({ image_url: ImageInputs.dataUrl(image) })
if (image.type === "url") return Effect.succeed({ image_url: image.url })
return ImageInputs.invalid("Meta Images accepts image bytes and URLs")
})
const { outputFormat, responseFormat, reasoningStrength, toolEnablement, ...native } = request.options ?? {}
const payload = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Body))(
mergeJsonRecords(
{
model: request.model.id,
prompt: request.prompt,
images: images.length === 0 ? undefined : images,
output_format: outputFormat,
response_format: responseFormat,
reasoning_strength: reasoningStrength,
tool_enablement: toolEnablement,
},
native,
http?.body,
),
)
const body = ProviderShared.encodeJson(payload)
const url = new URL(`${input.baseURL.replace(/\/$/, "")}/images/${images.length === 0 ? "generations" : "edits"}`)
Object.entries(http?.query ?? {}).forEach(([key, value]) => url.searchParams.set(key, value))
const headers = yield* Auth.toEffect(input.auth)({
request,
method: "POST",
url: url.toString(),
body,
headers: Headers.fromInput({ ...input.headers, ...http?.headers }),
})
const response = yield* execute(
HttpClientRequest.post(url.toString()).pipe(
HttpClientRequest.setHeaders(headers),
HttpClientRequest.bodyText(body, "application/json"),
),
)
const output = yield* ProviderShared.imageResponse("meta-images", "Meta Images", response)
const decoded = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Response))(output.body).pipe(
Effect.mapError((cause) => output.invalid("Meta Images returned an invalid response", cause)),
)
const format = decoded.output_format ?? payload.output_format ?? "webp"
const generated = yield* Effect.forEach(decoded.data, (item, index) => {
if (item.b64_json)
return Effect.fromResult(Encoding.decodeBase64(item.b64_json)).pipe(
Effect.mapError((cause) => output.invalid(`Meta Images result ${index} contains invalid base64`, cause)),
Effect.map((data) => new GeneratedImage({ mediaType: `image/${format}`, data })),
)
if (item.url) return Effect.succeed(new GeneratedImage({ mediaType: `image/${format}`, data: item.url }))
return output.invalid(`Meta Images result ${index} has neither image data nor a URL`)
})
if (generated.length === 0) return yield* output.invalid("Meta Images returned no images")
return new ImageResponse({
images: generated,
usage:
decoded.usage === undefined
? undefined
: new Usage({
inputTokens: decoded.usage.input_tokens,
outputTokens: decoded.usage.output_tokens,
totalTokens: decoded.usage.total_tokens,
providerMetadata: { meta: decoded.usage },
}),
providerMetadata: { meta: { outputFormat: format } },
})
}),
}
return ImageModel.make<ImageOptions>({ id: input.id, provider: "meta", route, http: input.http })
}
export * as MetaImages from "./meta-images.js"
@@ -0,0 +1,52 @@
import { Effect, Schema } from "effect"
import { Protocol } from "../route/protocol.js"
import type { LLMRequest } from "../schema/index.js"
import { AnthropicMessages } from "./anthropic-messages.js"
import { MetaResponses } from "./meta-responses.js"
import { JsonObject, optionalArray, ProviderShared } from "./shared.js"
const WebSearch = Schema.Struct({
type: Schema.Literal("web_search"),
name: Schema.Literal("web_search"),
user_location: MetaResponses.WebSearch.fields.user_location,
})
const Body = Schema.Struct({
...AnthropicMessages.AnthropicMessagesBody.fields,
tools: optionalArray(
Schema.Union([
Schema.Struct({ name: Schema.String, description: Schema.String, input_schema: JsonObject }),
WebSearch,
]),
),
})
const fromRequest = Effect.fn("MetaMessages.fromRequest")(function* (request: LLMRequest) {
const projected = ProviderShared.flattenToolRequest(request)
const body = yield* AnthropicMessages.protocol.body.from(projected.request)
return {
...body,
tools:
body.tools === undefined
? undefined
: yield* Effect.forEach(body.tools, (tool, index) =>
Effect.gen(function* () {
const native = projected.tools[index]?.native
if (native === undefined) return tool
const search = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(MetaResponses.WebSearch))(
native.meta,
)
if (search.search_context_size !== undefined)
return yield* ProviderShared.invalidRequest("Meta Messages does not support searchContextSize")
return { type: "web_search" as const, name: "web_search" as const, user_location: search.user_location }
}),
),
}
})
export const protocol = Protocol.make({
id: "meta-messages",
body: { schema: Body, from: fromRequest },
stream: AnthropicMessages.protocol.stream,
})
export * as MetaMessages from "./meta-messages.js"
+238
View File
@@ -0,0 +1,238 @@
import { Effect, Encoding, Schema } from "effect"
import { Protocol } from "../route/protocol.js"
import { HttpTransport } from "../route/transport/index.js"
import { LLMEvent, LLMRequest, Message, ToolResultPart } from "../schema/index.js"
import { OpenResponses } from "./open-responses.js"
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared.js"
import { ResponsesHostedTools } from "./utils/responses-hosted-tools.js"
import { ToolSchemaProjection } from "./utils/tool-schema.js"
import { MetaImage } from "./utils/meta-image.js"
const ADAPTER = "meta-responses"
const NAME = "Meta Responses"
export const WebSearch = Schema.Struct({
type: Schema.Literal("web_search"),
search_context_size: Schema.optional(Schema.String),
user_location: Schema.optional(
Schema.Struct({
type: Schema.Literal("approximate"),
city: Schema.optional(Schema.String),
region: Schema.optional(Schema.String),
country: Schema.optional(Schema.String),
timezone: Schema.optional(Schema.String),
}),
),
})
export const ImageGeneration = Schema.Struct({
type: Schema.Literal("image_generation"),
size: Schema.optional(Schema.String),
output_format: Schema.optional(Schema.String),
reasoning_strength: Schema.optional(Schema.String),
enable_image_search: Schema.optional(Schema.Boolean),
enable_web_search: Schema.optional(Schema.Boolean),
enable_shell: Schema.optional(Schema.Boolean),
})
const NativeTool = Schema.Union([WebSearch, ImageGeneration])
const ImageItem = Schema.Struct({
type: Schema.Literal("image_generation_call"),
id: Schema.String,
status: Schema.optional(Schema.String),
result: optionalNull(Schema.String),
output_format: Schema.optional(Schema.String),
error: Schema.optional(Schema.Unknown),
})
const Body = Schema.Struct({
...OpenResponses.coreFields,
input: Schema.Array(Schema.Union([OpenResponses.InputItem, ImageItem])),
tools: optionalArray(Schema.Union([OpenResponses.Tool, NativeTool])),
stream: Schema.Literal(true),
})
const MessageAnnotations = Schema.Struct({
content: Schema.Array(Schema.Struct({ annotations: optionalArray(JsonObject) })),
})
interface ParserState extends OpenResponses.ParserState {
readonly completedItems: ReadonlySet<string>
}
const adapter = {
id: ADAPTER,
name: NAME,
restoreHostedToolItem: (item: unknown) => (Schema.is(ImageItem)(item) ? item : undefined),
} satisfies OpenResponses.ProviderAdapter
const fromRequest = Effect.fn("MetaResponses.fromRequest")(function* (request: LLMRequest) {
const key = request.model.route.providerMetadataKey ?? String(request.model.provider)
const projected = ProviderShared.flattenToolRequest(
LLMRequest.update(request, {
messages: request.messages.map((message) =>
Message.make({
...message,
content: message.content.map((part) => {
if (
part.type !== "tool-result" ||
!part.providerExecuted ||
part.name !== "image_generation" ||
part.result.type !== "content" ||
part.providerMetadata?.[key]?.itemId !== part.id
)
return part
// Meta's signed image ID carries edit state; replay the handle, not the image bytes as a user message.
return ToolResultPart.make({
...part,
result: {
type: "json",
value: { type: "image_generation_call", id: part.id, status: "completed", result: null },
},
})
}),
}),
),
}),
)
return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Body))({
...(yield* OpenResponses.lowerConversation(projected.request, adapter)),
...OpenResponses.lowerGeneration(request),
tools:
projected.tools.length === 0
? undefined
: yield* Effect.forEach(projected.tools, (tool) =>
Effect.gen(function* () {
if (tool.native === undefined)
return yield* OpenResponses.lowerTool(
NAME,
tool,
ToolSchemaProjection.modelCompatibility(tool.inputSchema, request.model.compatibility?.toolSchema),
)
return yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(NativeTool))(tool.native.meta)
}),
),
tool_choice:
OpenResponses.allowedToolChoice(request) ??
(request.toolChoice ? yield* OpenResponses.lowerToolChoice(NAME, request.toolChoice) : undefined),
})
})
const HOSTED_TOOLS = {
web_search_call: { name: "web_search", input: (item) => item.action ?? {} },
image_generation_call: {
name: "image_generation",
input: () => ({}),
result: Effect.fn("MetaResponses.imageResult")(function* (raw: ResponsesHostedTools.Item) {
const item = yield* Schema.decodeUnknownEffect(ImageItem)(raw).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
ADAPTER,
"Meta returned an invalid image item",
ProviderShared.encodeJson(raw),
cause,
),
),
)
if (item.error !== undefined && item.error !== null) return { type: "error" as const, value: item.error }
if (!item.result)
return yield* ProviderShared.eventError(
ADAPTER,
"Meta returned an image without data",
ProviderShared.encodeJson(raw),
)
const data = yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
ADAPTER,
"Meta returned invalid image base64",
ProviderShared.encodeJson(raw),
cause,
),
),
)
const mime = MetaImage.mediaType(data, item.output_format)
return {
type: "content" as const,
value: [{ type: "file" as const, uri: `data:${mime};base64,${item.result}`, mime }],
}
}),
},
} satisfies ResponsesHostedTools.Definitions
const onEvent = Effect.fn("MetaResponses.onEvent")(function* (
state: OpenResponses.ParserState,
input: OpenResponses.Event,
) {
const event = OpenResponses.normalize(state, input)
if (event.type === "response.output_item.done" && event.item && ResponsesHostedTools.isItem(event.item, HOSTED_TOOLS))
return yield* ResponsesHostedTools.onDone(state, event.item, HOSTED_TOOLS)
const result = yield* OpenResponses.step(state, event)
if (event.type !== "response.output_item.done" || event.item?.type !== "message") return result
const message = yield* Schema.decodeUnknownEffect(MessageAnnotations)(event.item).pipe(
Effect.mapError((cause) =>
ProviderShared.eventError(
ADAPTER,
"Meta returned invalid message annotations",
ProviderShared.encodeJson(event),
cause,
),
),
)
const annotations = message.content.flatMap((part) => part.annotations ?? [])
if (annotations.length === 0) return result
return [
result[0],
result[1].map((item) =>
LLMEvent.is.textEnd(item)
? LLMEvent.textEnd({
...item,
providerMetadata: {
...item.providerMetadata,
[state.providerMetadataKey]: { ...item.providerMetadata?.[state.providerMetadataKey], annotations },
},
})
: item,
),
] satisfies OpenResponses.StepResult
})
const step = Effect.fn("MetaResponses.step")(function* (state: ParserState, input: OpenResponses.Event) {
const completedItems = new Set(state.completedItems)
const event = OpenResponses.normalize(state, input)
if (event.type === "response.output_item.done" && event.item && completedItems.has(event.item.id))
return [state, []] as const
const events: LLMEvent[] = []
let current: OpenResponses.ParserState = state
// Muse Image delivers its image and optional summary only in response.completed.
// Recover terminal-only items in order, without duplicating Spark's streamed items.
if (event.type === "response.completed") {
for (const [index, item] of (event.response?.output ?? []).entries()) {
const done = OpenResponses.normalize(current, { type: "response.output_item.done", item, output_index: index })
// Spark changes reasoning IDs in the terminal snapshot; output indices still identify the streamed items.
if (!done.item || completedItems.has(done.item.id) || completedItems.has(state.outputItems[index] ?? "")) continue
const result = yield* onEvent(current, done)
current = result[0]
events.push(...result[1])
completedItems.add(done.item.id)
}
}
const result = yield* onEvent(current, event)
if (event.type === "response.output_item.done" && event.item) completedItems.add(event.item.id)
return [{ ...result[0], completedItems }, [...events, ...result[1]]] as const
})
export const protocol = Protocol.make({
id: ADAPTER,
body: { schema: Body, from: fromRequest },
stream: {
event: OpenResponses.protocol.stream.event,
initial: (request): ParserState => ({ ...OpenResponses.initial(request, adapter), completedItems: new Set() }),
step,
terminal: OpenResponses.terminal,
},
})
export const httpTransport = HttpTransport.sseJson.with<Schema.Schema.Type<typeof Body>>()
export * as MetaResponses from "./meta-responses.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Option, Schema } from "effect"
import type { Content } from "@opencode-ai/schema/tool"
import type { Content } from "@opencode/schema/tool"
import { HttpTransport } from "../route/transport/index.js"
import { Protocol } from "../route/protocol.js"
import {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, Schema } from "effect"
import { Tool } from "@opencode-ai/schema/tool"
import { Tool } from "@opencode/schema/tool"
import { Route } from "../route/client.js"
import { Auth } from "../route/auth.js"
import { Endpoint } from "../route/endpoint.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Buffer } from "node:buffer"
import { Tool } from "@opencode-ai/schema/tool"
import { Tool } from "@opencode/schema/tool"
import { Effect, Schema, Stream } from "effect"
import * as Sse from "effect/unstable/encoding/Sse"
import { Headers, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
@@ -0,0 +1,11 @@
// Responses image items can omit output_format, including when PNG/JPEG was requested.
export const mediaType = (data: Uint8Array, format?: string) => {
if (format !== undefined) return `image/${format}`
if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) return "image/png"
if (data[0] === 255 && data[1] === 216 && data[2] === 255) return "image/jpeg"
if (new TextDecoder().decode(data.slice(0, 4)) === "RIFF" && new TextDecoder().decode(data.slice(8, 12)) === "WEBP")
return "image/webp"
return "application/octet-stream"
}
export * as MetaImage from "./meta-image.js"
+75
View File
@@ -0,0 +1,75 @@
import { Effect, Schema } from "effect"
import { Protocol } from "../route/protocol.js"
import type { LanguageModelCompatibility, LLMRequest } from "../schema/index.js"
import { OpenAIChat } from "./openai-chat.js"
import { ProviderShared } from "./shared.js"
export type ReasoningEffort = "none" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" | (string & {})
export type OptionsInput = {
readonly reasoningEffort?: ReasoningEffort
readonly thinking?: {
readonly type?: "enabled" | "disabled" | (string & {})
/** False retains historical reasoning; omission preserves the endpoint's default. */
readonly clear_thinking?: boolean
}
readonly toolStream?: boolean
readonly doSample?: boolean
readonly responseFormat?: { readonly type: "text" | "json_object" | (string & {}) }
readonly requestID?: string
readonly userID?: string
}
const Options = Schema.Struct({
reasoningEffort: Schema.optional(Schema.String),
thinking: Schema.optional(
Schema.Struct({ type: Schema.optional(Schema.String), clear_thinking: Schema.optional(Schema.Boolean) }),
),
toolStream: Schema.optional(Schema.Boolean),
doSample: Schema.optional(Schema.Boolean),
responseFormat: Schema.optional(Schema.Struct({ type: Schema.String })),
requestID: Schema.optional(Schema.String),
userID: Schema.optional(Schema.String),
})
const Body = Schema.Struct({
...OpenAIChat.bodyFields,
thinking: Options.fields.thinking,
do_sample: Options.fields.doSample,
response_format: Options.fields.responseFormat,
request_id: Options.fields.requestID,
user_id: Options.fields.userID,
})
const fromRequest = Effect.fn("ZAIChat.fromRequest")(function* (request: LLMRequest) {
const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(request.providerOptions ?? {})
const body = yield* OpenAIChat.protocol.body.from(request)
return {
...body,
thinking: options.thinking,
// Tool streaming was introduced in GLM-4.6; older models must not receive the opt-in.
tool_stream:
options.toolStream ??
(body.tools?.length && /^glm-(?:4\.[67]|5(?:[.-]|$))/i.test(request.model.id) ? true : undefined),
do_sample: options.doSample,
response_format: options.responseFormat,
request_id: options.requestID,
user_id: options.userID,
}
})
export const compatibility = {
maxTokensField: "max_tokens",
supportsStore: false,
supportsStrictMode: false,
reasoningField: "reasoning_content",
zaiToolStream: false,
} satisfies LanguageModelCompatibility
export const protocol = Protocol.make({
id: "zai-chat",
body: { schema: Body, from: fromRequest },
stream: OpenAIChat.protocol.stream,
})
export * as ZAIChat from "./zai-chat.js"
+39
View File
@@ -0,0 +1,39 @@
import { Effect, Schema } from "effect"
import { Protocol } from "../route/protocol.js"
import { LLMRequest } from "../schema/index.js"
import { AnthropicMessages } from "./anthropic-messages.js"
import { ProviderShared } from "./shared.js"
import type { ZAIChat } from "./zai-chat.js"
export type OptionsInput = {
readonly effort?: ZAIChat.ReasoningEffort
readonly thinking?: { readonly type: "enabled" | "adaptive" | "disabled" | (string & {}) }
}
const Options = Schema.Struct({
effort: Schema.optional(Schema.String),
thinking: Schema.optional(Schema.Struct({ type: Schema.String })),
})
const Body = Schema.Struct({
...AnthropicMessages.AnthropicMessagesBody.fields,
thinking: Options.fields.thinking,
})
const fromRequest = Effect.fn("ZAIMessages.fromRequest")(function* (request: LLMRequest) {
const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Options))(request.providerOptions ?? {})
// Z.AI accepts enabled thinking without Anthropic's mandatory token budget.
const body = yield* AnthropicMessages.protocol.body.from(
LLMRequest.update(request, {
providerOptions: { ...request.providerOptions, thinking: undefined },
}),
)
return { ...body, thinking: options.thinking }
})
export const protocol = Protocol.make({
id: "zai-messages",
body: { schema: Body, from: fromRequest },
stream: AnthropicMessages.protocol.stream,
})
export * as ZAIMessages from "./zai-messages.js"
+4
View File
@@ -16,7 +16,10 @@ export * as GoogleVertexChat from "./google-vertex-chat.js"
export * as GoogleVertexMessages from "./google-vertex-messages.js"
export * as GoogleVertexResponses from "./google-vertex-responses.js"
export * as Groq from "./groq.js"
export * as Meta from "./meta.js"
export * as MiniMax from "./minimax.js"
export * as Mistral from "./mistral.js"
export * as Moonshot from "./moonshot.js"
export * as OpenAI from "./openai.js"
export * as OpenAICompatible from "./openai-compatible.js"
export * as OpenAICompatibleResponses from "./openai-compatible-responses.js"
@@ -24,3 +27,4 @@ export * as OpenRouter from "./openrouter.js"
export * as TogetherAI from "./togetherai.js"
export * as XAI from "./xai.js"
export * as ZAI from "./zai.js"
export * as ZAICodingPlan from "./zai-coding-plan.js"
+182
View File
@@ -0,0 +1,182 @@
import type { ProviderPackage } from "../provider-package.js"
import { OpenAIChat } from "../protocols/openai-chat.js"
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
import { MetaResponses } from "../protocols/meta-responses.js"
import { MetaMessages } from "../protocols/meta-messages.js"
import { MetaImages } from "../protocols/meta-images.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 { HttpOptions, ProviderID, ToolDefinition, type ModelID } from "../schema/index.js"
import type { OpenResponsesProviderOptionsInput } from "./open-responses-options.js"
export const id = ProviderID.make("meta")
const baseURL = "https://api.meta.ai/v1"
export type ProviderOptionsInput = OpenResponsesProviderOptionsInput &
Pick<AnthropicMessages.OptionsInput, "thinking" | "effort">
export type MessagesOptionsInput = Pick<
AnthropicMessages.OptionsInput,
"thinking" | "effort" | "outputConfig" | "output_config" | "serviceTier" | "service_tier" | "metadata"
> & { readonly [key: string]: unknown }
export type ImageOptions = MetaImages.ImageOptions
export interface WebSearchOptions {
readonly searchContextSize?: "low" | "medium" | "high" | (string & {})
readonly userLocation?: {
readonly city?: string
readonly region?: string
readonly country?: string
readonly timezone?: string
}
}
export const webSearch = (options: WebSearchOptions = {}) =>
ToolDefinition.make({
name: "web_search",
description: "Search the web with Meta's hosted search tool.",
inputSchema: { type: "object", properties: {}, additionalProperties: false },
native: {
meta: {
type: "web_search",
search_context_size: options.searchContextSize,
user_location:
options.userLocation === undefined ? undefined : { type: "approximate", ...options.userLocation },
},
},
})
export interface ImageGenerationOptions {
readonly size?: string
readonly outputFormat?: "webp" | "png" | "jpeg" | (string & {})
readonly reasoningStrength?: "low" | "high" | (string & {})
readonly enableImageSearch?: boolean
readonly enableWebSearch?: boolean
readonly enableShell?: boolean
}
export const imageGeneration = (options: ImageGenerationOptions = {}) =>
ToolDefinition.make({
name: "image_generation",
description: "Generate or edit an image with Muse Image.",
inputSchema: { type: "object", properties: {}, additionalProperties: false },
native: {
meta: {
type: "image_generation",
size: options.size,
output_format: options.outputFormat,
reasoning_strength: options.reasoningStrength,
enable_image_search: options.enableImageSearch,
enable_web_search: options.enableWebSearch,
enable_shell: options.enableShell,
},
},
})
export type LanguageModelOptions = Omit<RouteDefaultsInput, "providerOptions"> &
ProviderAuthOption<"optional"> & {
readonly baseURL?: string
readonly providerOptions?: ProviderOptionsInput
}
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly providerOptions?: ProviderOptionsInput
}
const responsesRoute = Route.make({
id: "meta-responses",
provider: id,
providerMetadataKey: "meta",
protocol: MetaResponses.protocol,
endpoint: Endpoint.path("/responses", { baseURL }),
// Meta Responses does not support WebSocket upgrades; always use HTTP/SSE.
transport: MetaResponses.httpTransport,
defaults: { providerOptions: { store: false, include: ["reasoning.encrypted_content"] } },
})
const chatRoute = Route.make({
id: "meta-chat",
provider: id,
providerMetadataKey: "meta",
protocol: OpenAIChat.protocol,
endpoint: Endpoint.path("/chat/completions", { baseURL }),
framing: OpenAIChat.framing,
})
const messagesRoute = Route.make({
id: "meta-messages",
provider: id,
providerMetadataKey: "meta",
protocol: MetaMessages.protocol,
endpoint: Endpoint.path("/messages", { baseURL }),
framing: AnthropicMessages.framing,
defaults: { providerOptions: { thinking: { type: "adaptive", display: "omitted" } } },
})
export const routes = [responsesRoute, chatRoute, messagesRoute]
export const configure = (input: LanguageModelOptions = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL: endpoint, ...defaults } = input
const options = {
...defaults,
endpoint: { baseURL: endpoint ?? baseURL },
auth: AuthOptions.bearer(input, "META_API_KEY"),
}
const configuredResponses = responsesRoute.with(options)
const configuredChat = chatRoute.with(options)
const configuredMessages = messagesRoute.with(options)
const responses = (modelID: string | ModelID) =>
configuredResponses.model<OpenResponsesProviderOptionsInput>({ id: modelID })
const chat = (modelID: string | ModelID) =>
configuredChat.model<OpenResponsesProviderOptionsInput>({
id: modelID,
compatibility: { maxTokensField: "max_completion_tokens", supportsStore: false },
})
const messages = (modelID: string | ModelID) =>
configuredMessages.model<MessagesOptionsInput>({
id: modelID,
compatibility: { requireSignature: false },
})
const image = (modelID: string | ModelID) =>
MetaImages.model({
id: modelID,
baseURL: endpoint ?? baseURL,
auth: options.auth,
headers: input.headers,
http: input.http === undefined ? undefined : HttpOptions.make(input.http),
})
return { id, model: responses, responses, chat, messages, image, configure }
}
export const provider = configure()
export const responses = provider.responses
export const chat = provider.chat
export const messages = provider.messages
export const image = provider.image
export const model: ProviderPackage.Definition<Settings, OpenResponsesProviderOptionsInput>["model"] = (
modelID,
settings,
) => fromSettings(settings).responses(modelID)
export const chatModel: ProviderPackage.Definition<Settings, OpenResponsesProviderOptionsInput>["model"] = (
modelID,
settings,
) => fromSettings(settings).chat(modelID)
export const messagesModel: ProviderPackage.Definition<Settings, MessagesOptionsInput>["model"] = (modelID, settings) =>
fromSettings(settings).messages(modelID)
function fromSettings(settings: Settings) {
return configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
})
}
export * as Meta from "./meta.js"
+2
View File
@@ -0,0 +1,2 @@
export { chatModel as model } from "../meta.js"
export type { Settings } from "../meta.js"
@@ -0,0 +1,2 @@
export { messagesModel as model } from "../meta.js"
export type { Settings } from "../meta.js"
@@ -0,0 +1,2 @@
export { model } from "../meta.js"
export type { Settings } from "../meta.js"
+144
View File
@@ -0,0 +1,144 @@
import { Effect, Schema } from "effect"
import type { ProviderPackage } from "../provider-package.js"
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
import { OpenAIChat } from "../protocols/openai-chat.js"
import { OpenResponses } from "../protocols/open-responses.js"
import { ProviderShared } from "../protocols/shared.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 { Framing } from "../route/framing.js"
import { Protocol } from "../route/protocol.js"
import { ProviderID, type LLMRequest, type ModelID } from "../schema/index.js"
export const id = ProviderID.make("minimax")
export type MessagesOptionsInput = {
/** M3 defaults to disabled; M2.x always thinks. */
readonly thinking?: { readonly type: "adaptive" | "disabled" }
readonly metadata?: AnthropicMessages.OptionsInput["metadata"]
}
export type ChatOptionsInput = {
/** M3 defaults to adaptive; M2.x always thinks. */
readonly thinking?: { readonly type: "adaptive" | "disabled" | (string & {}) }
/** Separates reasoning from text. Defaults to true. */
readonly reasoningSplit?: boolean
}
export type ResponsesOptionsInput = {
/** M3 defaults to none. Other supported values enable thinking without changing its depth. */
readonly reasoningEffort?: "none" | "minimal" | "low" | "medium" | "high" | (string & {})
}
export type ProviderOptionsInput = MessagesOptionsInput | ChatOptionsInput | ResponsesOptionsInput
export type Config = Omit<RouteDefaultsInput, "providerOptions"> &
ProviderAuthOption<"optional"> & {
/** Overrides the selected API's base URL, including its version prefix. */
readonly baseURL?: string
readonly providerOptions?: ProviderOptionsInput
}
export interface Settings<Options = MessagesOptionsInput> extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly providerOptions?: Options
}
const ChatOptions = Schema.Struct({
thinking: Schema.optional(Schema.Struct({ type: Schema.String })),
reasoningSplit: Schema.optional(Schema.Boolean),
})
const chatProtocol = Protocol.make({
id: "minimax-chat",
body: {
schema: Schema.Struct({
...OpenAIChat.bodyFields,
thinking: ChatOptions.fields.thinking,
reasoning_split: Schema.Boolean,
}),
from: Effect.fn("MiniMax.chatFromRequest")(function* (request: LLMRequest) {
const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(ChatOptions))(
request.providerOptions ?? {},
)
return {
...(yield* OpenAIChat.protocol.body.from(request)),
thinking: options.thinking,
// MiniMax otherwise embeds <think> tags in ordinary assistant text.
reasoning_split: options.reasoningSplit ?? true,
}
}),
},
stream: OpenAIChat.protocol.stream,
})
const messagesRoute = Route.make({
id: "minimax-messages",
provider: id,
providerMetadataKey: "minimax",
protocol: AnthropicMessages.protocol,
endpoint: Endpoint.path("/messages", { baseURL: "https://api.minimax.io/anthropic/v1" }),
framing: AnthropicMessages.framing,
headers: () => ({ "anthropic-version": "2023-06-01" }),
})
const chatRoute = Route.make({
id: "minimax-chat",
provider: id,
providerMetadataKey: "minimax",
protocol: chatProtocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: "https://api.minimax.io/v1" }),
framing: OpenAIChat.framing,
})
const responsesRoute = Route.make({
id: "minimax-responses",
provider: id,
providerMetadataKey: "minimax",
protocol: OpenResponses.protocol,
endpoint: Endpoint.path("/responses", { baseURL: "https://api.minimax.io/v1" }),
framing: Framing.sse,
})
export const routes = [messagesRoute, chatRoute, responsesRoute]
export const configure = (input: Config = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL, ...rest } = input
const defaults = {
...rest,
endpoint: baseURL === undefined ? undefined : { baseURL },
auth: AuthOptions.bearer(input, "MINIMAX_API_KEY"),
}
const messages = (modelID: string | ModelID) =>
messagesRoute.with(defaults).model<MessagesOptionsInput>({ id: modelID })
const chat = (modelID: string | ModelID) =>
chatRoute.with(defaults).model<ChatOptionsInput>({
id: modelID,
compatibility: { supportsStore: false, supportsStrictMode: false },
})
const responses = (modelID: string | ModelID) =>
responsesRoute.with(defaults).model<ResponsesOptionsInput>({ id: modelID })
return { id, model: messages, messages, chat, responses, configure }
}
export const provider = configure()
export const model: ProviderPackage.Definition<Settings<MessagesOptionsInput>, MessagesOptionsInput>["model"] = (
modelID,
settings,
) =>
configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).model(modelID)
export const messages = provider.messages
export const chat = provider.chat
export const responses = provider.responses
export * as MiniMax from "./minimax.js"
+13
View File
@@ -0,0 +1,13 @@
import type { ProviderPackage } from "../../provider-package.js"
import { MiniMax } from "../minimax.js"
export type Settings = MiniMax.Settings<MiniMax.ChatOptionsInput>
export const model: ProviderPackage.Definition<Settings, MiniMax.ChatOptionsInput>["model"] = (modelID, settings) =>
MiniMax.configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).chat(modelID)
@@ -0,0 +1 @@
export { model, type Settings, type MessagesOptionsInput } from "../minimax.js"
@@ -0,0 +1,16 @@
import type { ProviderPackage } from "../../provider-package.js"
import { MiniMax } from "../minimax.js"
export type Settings = MiniMax.Settings<MiniMax.ResponsesOptionsInput>
export const model: ProviderPackage.Definition<Settings, MiniMax.ResponsesOptionsInput>["model"] = (
modelID,
settings,
) =>
MiniMax.configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).responses(modelID)
+145
View File
@@ -0,0 +1,145 @@
import { Effect, Schema } from "effect"
import type { ProviderPackage } from "../provider-package.js"
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
import { OpenAIChat } from "../protocols/openai-chat.js"
import { OpenResponses } from "../protocols/open-responses.js"
import { ProviderShared } from "../protocols/shared.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 { Framing } from "../route/framing.js"
import { Protocol } from "../route/protocol.js"
import { ProviderID, type LLMRequest, type ModelID } from "../schema/index.js"
export const id = ProviderID.make("moonshotai")
export type ReasoningEffort = "low" | "high" | "max" | (string & {})
export type ChatOptionsInput = {
/** K3 always reasons; omitted effort uses the model's default. */
readonly reasoningEffort?: ReasoningEffort
/** K2.6 supports disabling thinking; K2.7 Code always thinks and preserves reasoning. */
readonly thinking?: {
readonly type: "enabled" | "disabled" | (string & {})
readonly keep?: "all" | (string & {}) | null
}
}
export type MessagesOptionsInput = {
readonly effort?: ReasoningEffort
readonly metadata?: AnthropicMessages.OptionsInput["metadata"]
}
export type ResponsesOptionsInput = {
readonly reasoningEffort?: ReasoningEffort
readonly safetyIdentifier?: string
}
export type Config = Omit<RouteDefaultsInput, "providerOptions"> &
ProviderAuthOption<"optional"> & {
/** Overrides the selected API's base URL, including its version prefix. */
readonly baseURL?: string
readonly providerOptions?: ChatOptionsInput | MessagesOptionsInput | ResponsesOptionsInput
}
export interface Settings<Options = ChatOptionsInput> extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly providerOptions?: Options
}
const ChatOptions = Schema.Struct({
reasoningEffort: Schema.optional(Schema.String),
thinking: Schema.optional(
Schema.Struct({ type: Schema.String, keep: Schema.optional(Schema.NullOr(Schema.String)) }),
),
})
const chatProtocol = Protocol.make({
id: "moonshot-chat",
body: {
schema: Schema.Struct({ ...OpenAIChat.bodyFields, thinking: ChatOptions.fields.thinking }),
from: Effect.fn("Moonshot.chatFromRequest")(function* (request: LLMRequest) {
const options = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(ChatOptions))(
request.providerOptions ?? {},
)
return { ...(yield* OpenAIChat.protocol.body.from(request)), thinking: options.thinking }
}),
},
stream: OpenAIChat.protocol.stream,
})
const chatRoute = Route.make({
id: "moonshot-chat",
provider: id,
providerMetadataKey: "moonshot",
protocol: chatProtocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: "https://api.moonshot.ai/v1" }),
framing: OpenAIChat.framing,
})
const messagesRoute = Route.make({
id: "moonshot-messages",
provider: id,
providerMetadataKey: "moonshot",
protocol: AnthropicMessages.protocol,
endpoint: Endpoint.path("/messages", { baseURL: "https://api.moonshot.ai/anthropic/v1" }),
framing: AnthropicMessages.framing,
})
const responsesRoute = Route.make({
id: "moonshot-responses",
provider: id,
providerMetadataKey: "moonshot",
protocol: OpenResponses.protocol,
endpoint: Endpoint.path("/responses", { baseURL: "https://api.moonshot.ai/v1" }),
framing: Framing.sse,
})
export const routes = [chatRoute, messagesRoute, responsesRoute]
export const configure = (input: Config = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL, ...rest } = input
const defaults = {
...rest,
endpoint: baseURL === undefined ? undefined : { baseURL },
auth: AuthOptions.bearer(input, ["MOONSHOT_API_KEY", "MOONSHOTAI_API_KEY"]),
}
const chat = (modelID: string | ModelID) =>
chatRoute.with(defaults).model<ChatOptionsInput>({
id: modelID,
compatibility: {
maxTokensField: "max_tokens",
supportsStore: false,
supportsStrictMode: false,
toolSchema: "moonshot",
reasoningField: "reasoning_content",
},
})
const messages = (modelID: string | ModelID) =>
messagesRoute.with(defaults).model<MessagesOptionsInput>({
id: modelID,
compatibility: { requireSignature: false, toolSchema: "moonshot" },
})
const responses = (modelID: string | ModelID) =>
responsesRoute
.with(defaults)
.model<ResponsesOptionsInput>({ id: modelID, compatibility: { toolSchema: "moonshot" } })
return { id, model: chat, chat, messages, responses, configure }
}
export const provider = configure()
export const chat = provider.chat
export const messages = provider.messages
export const responses = provider.responses
export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["model"] = (modelID, settings) =>
configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).model(modelID)
export * as Moonshot from "./moonshot.js"
@@ -0,0 +1 @@
export { model, type Settings } from "../moonshot.js"
@@ -0,0 +1,16 @@
import type { ProviderPackage } from "../../provider-package.js"
import { Moonshot } from "../moonshot.js"
export type Settings = Moonshot.Settings<Moonshot.MessagesOptionsInput>
export const model: ProviderPackage.Definition<Settings, Moonshot.MessagesOptionsInput>["model"] = (
modelID,
settings,
) =>
Moonshot.configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).messages(modelID)
@@ -0,0 +1,16 @@
import type { ProviderPackage } from "../../provider-package.js"
import { Moonshot } from "../moonshot.js"
export type Settings = Moonshot.Settings<Moonshot.ResponsesOptionsInput>
export const model: ProviderPackage.Definition<Settings, Moonshot.ResponsesOptionsInput>["model"] = (
modelID,
settings,
) =>
Moonshot.configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).responses(modelID)
@@ -0,0 +1,92 @@
import type { ProviderPackage } from "../provider-package.js"
import { AnthropicMessages } from "../protocols/anthropic-messages.js"
import { OpenAIChat } from "../protocols/openai-chat.js"
import { OpenResponses } from "../protocols/open-responses.js"
import { ZAIChat } from "../protocols/zai-chat.js"
import { ZAIMessages } from "../protocols/zai-messages.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 { Framing } from "../route/framing.js"
import { ProviderID, type ModelID } from "../schema/index.js"
export const id = ProviderID.make("zai-coding-plan")
export type ChatOptionsInput = ZAIChat.OptionsInput
export type MessagesOptionsInput = ZAIMessages.OptionsInput
export type ResponsesOptionsInput = { readonly reasoningEffort?: ZAIChat.ReasoningEffort }
export type Config = Omit<RouteDefaultsInput, "providerOptions"> &
ProviderAuthOption<"optional"> & {
/** Overrides the selected API's complete base URL. */
readonly baseURL?: string
readonly providerOptions?: ChatOptionsInput | MessagesOptionsInput | ResponsesOptionsInput
}
export interface Settings<Options = ChatOptionsInput> extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly providerOptions?: Options
}
const chatRoute = Route.make({
id: "zai-coding-chat",
provider: id,
providerMetadataKey: "zai",
protocol: ZAIChat.protocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: "https://api.z.ai/api/coding/paas/v4" }),
framing: OpenAIChat.framing,
})
const messagesRoute = Route.make({
id: "zai-coding-messages",
provider: id,
providerMetadataKey: "zai",
protocol: ZAIMessages.protocol,
endpoint: Endpoint.path("/messages", { baseURL: "https://api.z.ai/api/anthropic/v1" }),
framing: AnthropicMessages.framing,
headers: () => ({ "anthropic-version": "2023-06-01" }),
})
const responsesRoute = Route.make({
id: "zai-coding-responses",
provider: id,
providerMetadataKey: "zai",
protocol: OpenResponses.protocol,
endpoint: Endpoint.path("/responses", { baseURL: "https://api.z.ai/api/v1" }),
framing: Framing.sse,
})
export const routes = [chatRoute, messagesRoute, responsesRoute]
export const configure = (input: Config = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL, ...rest } = input
const defaults = {
...rest,
endpoint: baseURL === undefined ? undefined : { baseURL },
auth: AuthOptions.bearer(input, "ZAI_API_KEY"),
}
const chat = (modelID: string | ModelID) =>
chatRoute.with(defaults).model<ChatOptionsInput>({ id: modelID, compatibility: ZAIChat.compatibility })
const messages = (modelID: string | ModelID) =>
messagesRoute
.with(defaults)
.model<MessagesOptionsInput>({ id: modelID, compatibility: { requireSignature: false } })
const responses = (modelID: string | ModelID) =>
responsesRoute.with(defaults).model<ResponsesOptionsInput>({ id: modelID })
return { id, model: chat, chat, messages, responses, configure }
}
export const provider = configure()
export const chat = provider.chat
export const messages = provider.messages
export const responses = provider.responses
export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["model"] = (modelID, settings) =>
configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).model(modelID)
export * as ZAICodingPlan from "./zai-coding-plan.js"
@@ -0,0 +1 @@
export { model, type Settings } from "../zai-coding-plan.js"
@@ -0,0 +1,16 @@
import type { ProviderPackage } from "../../provider-package.js"
import { ZAICodingPlan } from "../zai-coding-plan.js"
export type Settings = ZAICodingPlan.Settings<ZAICodingPlan.MessagesOptionsInput>
export const model: ProviderPackage.Definition<Settings, ZAICodingPlan.MessagesOptionsInput>["model"] = (
modelID,
settings,
) =>
ZAICodingPlan.configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).messages(modelID)
@@ -0,0 +1,16 @@
import type { ProviderPackage } from "../../provider-package.js"
import { ZAICodingPlan } from "../zai-coding-plan.js"
export type Settings = ZAICodingPlan.Settings<ZAICodingPlan.ResponsesOptionsInput>
export const model: ProviderPackage.Definition<Settings, ZAICodingPlan.ResponsesOptionsInput>["model"] = (
modelID,
settings,
) =>
ZAICodingPlan.configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).responses(modelID)
+50 -3
View File
@@ -1,20 +1,53 @@
import type { ProviderPackage } from "../provider-package.js"
import { ZAIChat } from "../protocols/zai-chat.js"
import { ZAIImages } from "../protocols/zai-images.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 { HttpOptions, ProviderID, type ModelID } from "../schema/index.js"
export const id = ProviderID.make("zai")
export type Config = ProviderAuthOption<"optional"> & {
export type ChatOptionsInput = ZAIChat.OptionsInput
export type Config = Omit<RouteDefaultsInput, "providerOptions"> &
ProviderAuthOption<"optional"> & {
readonly baseURL?: string
readonly providerOptions?: ChatOptionsInput
}
export interface Settings extends ProviderPackage.Settings {
readonly apiKey?: string
readonly baseURL?: string
readonly headers?: Record<string, string>
readonly http?: HttpOptions.Input
readonly providerOptions?: ChatOptionsInput
}
export type { ZAIImageOptions } from "../protocols/zai-images.js"
const auth = (options: ProviderAuthOption<"optional">) => AuthOptions.bearer(options, "ZAI_API_KEY")
const chatRoute = Route.make({
id: "zai-chat",
provider: id,
providerMetadataKey: "zai",
protocol: ZAIChat.protocol,
endpoint: Endpoint.path("/chat/completions", { baseURL: "https://api.z.ai/api/paas/v4" }),
framing: OpenAIChat.framing,
})
export const routes = [chatRoute]
export const configure = (input: Config = {}) => {
const { apiKey: _apiKey, auth: _auth, baseURL, ...rest } = input
const chat = (modelID: string | ModelID) =>
chatRoute
.with({
...rest,
endpoint: baseURL === undefined ? undefined : { baseURL },
auth: auth(input),
})
.model<ChatOptionsInput>({ id: modelID, compatibility: ZAIChat.compatibility })
const image = (modelID: string | ModelID) =>
ZAIImages.model({
id: modelID,
@@ -26,6 +59,8 @@ export const configure = (input: Config = {}) => {
return {
id,
model: chat,
chat,
image,
configure,
}
@@ -33,3 +68,15 @@ export const configure = (input: Config = {}) => {
export const provider = configure()
export const image = provider.image
export const chat = provider.chat
export const model: ProviderPackage.Definition<Settings, ChatOptionsInput>["model"] = (modelID, settings) =>
configure({
apiKey: settings.apiKey,
baseURL: settings.baseURL,
headers: settings.headers,
http: settings.body === undefined ? undefined : { body: { ...settings.body } },
providerOptions: settings.providerOptions,
}).model(modelID)
export * as ZAI from "./zai.js"
+1
View File
@@ -0,0 +1 @@
export { model, type Settings } from "../zai.js"
+1 -1
View File
@@ -1,5 +1,5 @@
import { Schema } from "effect"
import { Tool } from "@opencode-ai/schema/tool"
import { Tool } from "@opencode/schema/tool"
import { ModelID, ProviderID, RouteID } from "./ids.js"
export const ProviderFailureClassification = Schema.Literals(["context-overflow", "payload-too-large"])
+1 -1
View File
@@ -1,5 +1,5 @@
import { Schema } from "effect"
import { LLM } from "@opencode-ai/schema/llm"
import { LLM } from "@opencode/schema/llm"
import { ContentBlockID, ToolCallID } from "./ids.js"
import {
Message,
+1 -1
View File
@@ -1,5 +1,5 @@
import { Schema } from "effect"
import { Tool } from "@opencode-ai/schema/tool"
import { Tool } from "@opencode/schema/tool"
import {
CacheHint,
CachePolicy,
+2
View File
@@ -163,6 +163,8 @@ export class LanguageModelCompatibility extends Schema.Class<LanguageModelCompat
supportsStrictMode: Schema.optional(Schema.Boolean),
zaiToolStream: Schema.optional(Schema.Boolean),
requireSignature: Schema.optional(Schema.Boolean),
/** Supports Anthropic's thinking-prefix mismatch controls. Overrides model-ID detection. */
supportsThinkingBlockBinding: Schema.optional(Schema.Boolean),
}) {}
export namespace LanguageModelCompatibility {
+1 -1
View File
@@ -1,5 +1,5 @@
import { Effect, JsonSchema, Schema } from "effect"
import { Tool } from "@opencode-ai/schema/tool"
import { Tool } from "@opencode/schema/tool"
import type {
ToolCallPart,
ToolDefinition as ToolDefinitionClass,
+8 -8
View File
@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test"
import { AIError, ImageInput, LanguageModel, LLM, LLMClient, Provider } from "@opencode-ai/ai"
import { Route, Protocol, WebSocketTransport } from "@opencode-ai/ai/route"
import { Provider as ProviderSubpath } from "@opencode-ai/ai/provider"
import { AIError, ImageInput, LanguageModel, LLM, LLMClient, Provider } from "@opencode/ai"
import { Route, Protocol, WebSocketTransport } from "@opencode/ai/route"
import { Provider as ProviderSubpath } from "@opencode/ai/provider"
import {
Baseten,
CloudflareAIGateway,
@@ -12,7 +12,7 @@ import {
OpenAICompatible,
OpenRouter,
XAI,
} from "@opencode-ai/ai/providers"
} from "@opencode/ai/providers"
import {
OpenAIChat,
OpenAICompatibleChat,
@@ -20,9 +20,9 @@ import {
OpenAIResponses,
OpenResponses,
OpenResponsesChannel,
} from "@opencode-ai/ai/protocols"
import * as AnthropicMessages from "@opencode-ai/ai/protocols/anthropic-messages"
import { TestLLM } from "@opencode-ai/ai/testing"
} from "@opencode/ai/protocols"
import * as AnthropicMessages from "@opencode/ai/protocols/anthropic-messages"
import { TestLLM } from "@opencode/ai/testing"
describe("public exports", () => {
test("root exposes app-facing runtime APIs", () => {
@@ -46,7 +46,7 @@ describe("public exports", () => {
})
test("provider barrels expose user-facing facades", async () => {
const { OpenAICompatibleResponses } = await import("@opencode-ai/ai/providers")
const { OpenAICompatibleResponses } = await import("@opencode/ai/providers")
expect(OpenAI.model).toBeFunction()
expect(OpenAI.provider.responses).toBe(OpenAI.responses)
@@ -0,0 +1,56 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": [
"prefix:meta-chat",
"provider:meta",
"protocol:openai-chat",
"tool",
"tool-loop",
"reasoning",
"usage",
"effort:low"
],
"name": "meta-chat/continues-a-generated-tool-call",
"recordedAt": "2026-09-07T16:54:19.772Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"Look up the current weather in Paris using lookup_weather before answering. After receiving the result, report Paris's weather in one short sentence.\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_weather\",\"description\":\"Look up the current weather for a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"auto\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"low\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cca-c092-7423-880d-204c05dc6645\",\"choices\":[{\"delta\":{\"content\":\"I'll look up the current weather in Paris now.\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800057,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-c092-7423-880d-204c05dc6645\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"id\":\"call_01a07ccac34671129bd9ef9a66fd3266\",\"type\":\"function\",\"function\":{\"name\":\"lookup_weather\",\"arguments\":\"\"}}]},\"finish_reason\":null,\"index\":0}],\"created\":1788800057,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-c092-7423-880d-204c05dc6645\",\"choices\":[{\"delta\":{\"tool_calls\":[{\"index\":0,\"function\":{\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}]},\"finish_reason\":null,\"index\":0}],\"created\":1788800057,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-c092-7423-880d-204c05dc6645\",\"choices\":[{\"delta\":{},\"finish_reason\":\"tool_calls\",\"index\":0}],\"created\":1788800057,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":139,\"prompt_tokens\":570,\"total_tokens\":709,\"completion_tokens_details\":{\"reasoning_tokens\":70},\"prompt_tokens_details\":{\"cached_tokens\":497}}}\n\ndata: [DONE]\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"Look up the current weather in Paris using lookup_weather before answering. After receiving the result, report Paris's weather in one short sentence.\"},{\"role\":\"assistant\",\"content\":\"I'll look up the current weather in Paris now.\",\"tool_calls\":[{\"id\":\"call_01a07ccac34671129bd9ef9a66fd3266\",\"type\":\"function\",\"function\":{\"name\":\"lookup_weather\",\"arguments\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}]},{\"role\":\"tool\",\"tool_call_id\":\"call_01a07ccac34671129bd9ef9a66fd3266\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperature\\\":\\\"18C\\\"}\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"lookup_weather\",\"description\":\"Look up the current weather for a city\",\"parameters\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false},\"strict\":false}}],\"tool_choice\":\"auto\",\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"low\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cca-c622-77e3-8e2c-9c52bac9acb5\",\"choices\":[{\"delta\":{\"content\":\"Paris is currently sunny with a\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800058,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-c622-77e3-8e2c-9c52bac9acb5\",\"choices\":[{\"delta\":{\"content\":\" temperature of 18°C\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800058,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-c622-77e3-8e2c-9c52bac9acb5\",\"choices\":[{\"delta\":{\"content\":\".\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800058,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-c622-77e3-8e2c-9c52bac9acb5\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800058,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":91,\"prompt_tokens\":666,\"total_tokens\":757,\"completion_tokens_details\":{\"reasoning_tokens\":69},\"prompt_tokens_details\":{\"cached_tokens\":497}}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,37 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": [
"prefix:meta-chat",
"provider:meta",
"protocol:openai-chat",
"text",
"reasoning",
"usage",
"effort:default"
],
"name": "meta-chat/streams-text-with-default-reasoning",
"recordedAt": "2026-09-07T16:55:12.540Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07ccb-8ae3-72b3-b321-b5163dda0714\",\"choices\":[{\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800109,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07ccb-8ae3-72b3-b321-b5163dda0714\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800109,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":363,\"prompt_tokens\":23,\"total_tokens\":386,\"completion_tokens_details\":{\"reasoning_tokens\":351},\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,29 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": ["prefix:meta-chat", "provider:meta", "protocol:openai-chat", "text", "reasoning", "usage", "effort:high"],
"name": "meta-chat/streams-text-with-high-reasoning",
"recordedAt": "2026-09-07T16:53:30.276Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"high\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cc9-fe49-7773-b7f3-0be27454d3c3\",\"choices\":[{\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800007,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cc9-fe49-7773-b7f3-0be27454d3c3\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800007,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":324,\"prompt_tokens\":23,\"total_tokens\":347,\"completion_tokens_details\":{\"reasoning_tokens\":312},\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,29 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": ["prefix:meta-chat", "provider:meta", "protocol:openai-chat", "text", "reasoning", "usage", "effort:low"],
"name": "meta-chat/streams-text-with-low-reasoning",
"recordedAt": "2026-09-07T16:53:23.751Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"low\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cc9-eb39-7ef3-b886-5d4215128b1d\",\"choices\":[{\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800002,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cc9-eb39-7ef3-b886-5d4215128b1d\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800002,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":157,\"prompt_tokens\":23,\"total_tokens\":180,\"completion_tokens_details\":{\"reasoning_tokens\":145},\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,29 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": ["prefix:meta-chat", "provider:meta", "protocol:openai-chat", "text", "reasoning", "usage", "effort:max"],
"name": "meta-chat/streams-text-with-max-reasoning",
"recordedAt": "2026-09-07T16:53:33.749Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"max\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cca-10dd-7793-999d-e037320a91de\",\"choices\":[{\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800012,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-10dd-7793-999d-e037320a91de\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800012,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":195,\"prompt_tokens\":23,\"total_tokens\":218,\"completion_tokens_details\":{\"reasoning_tokens\":183},\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,37 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": [
"prefix:meta-chat",
"provider:meta",
"protocol:openai-chat",
"text",
"reasoning",
"usage",
"effort:medium"
],
"name": "meta-chat/streams-text-with-medium-reasoning",
"recordedAt": "2026-09-07T16:53:27.292Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"medium\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cc9-f131-7fb0-8895-9f0c3e79220c\",\"choices\":[{\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800004,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cc9-f131-7fb0-8895-9f0c3e79220c\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800004,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":273,\"prompt_tokens\":23,\"total_tokens\":296,\"completion_tokens_details\":{\"reasoning_tokens\":261},\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,37 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": [
"prefix:meta-chat",
"provider:meta",
"protocol:openai-chat",
"text",
"reasoning",
"usage",
"effort:minimal"
],
"name": "meta-chat/streams-text-with-minimal-reasoning",
"recordedAt": "2026-09-07T16:53:22.460Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"minimal\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cc9-e817-7bb1-8dc5-5197d9a4c73e\",\"choices\":[{\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800002,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cc9-e817-7bb1-8dc5-5197d9a4c73e\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800002,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":75,\"prompt_tokens\":23,\"total_tokens\":98,\"completion_tokens_details\":{\"reasoning_tokens\":63},\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\ndata: [DONE]\n\n"
}
}
]
}
@@ -0,0 +1,29 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": ["prefix:meta-chat", "provider:meta", "protocol:openai-chat", "text", "reasoning", "usage", "effort:xhigh"],
"name": "meta-chat/streams-text-with-xhigh-reasoning",
"recordedAt": "2026-09-07T16:53:32.103Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"reasoning_effort\":\"xhigh\",\"max_completion_tokens\":1024}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "data: {\"id\":\"chatcmpl-01a07cca-09de-7083-a0cc-2100de752456\",\"choices\":[{\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"index\":0}],\"created\":1788800010,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\"}\n\ndata: {\"id\":\"chatcmpl-01a07cca-09de-7083-a0cc-2100de752456\",\"choices\":[{\"delta\":{},\"finish_reason\":\"stop\",\"index\":0}],\"created\":1788800010,\"model\":\"muse-spark-1.3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"completion_tokens\":253,\"prompt_tokens\":23,\"total_tokens\":276,\"completion_tokens_details\":{\"reasoning_tokens\":241},\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\ndata: [DONE]\n\n"
}
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": ["prefix:meta-messages", "provider:meta", "protocol:meta-messages", "tool", "tool-loop", "reasoning"],
"name": "meta-messages/replays-encrypted-thinking-through-a-tool-loop",
"recordedAt": "2026-09-07T17:27:03.540Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Look up the current weather in Paris using lookup_weather. After receiving the result, report Paris's weather in one short sentence.\"}]}],\"tools\":[{\"name\":\"lookup_weather\",\"description\":\"Look up current weather\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"tool_choice\":{\"type\":\"auto\"},\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"adaptive\",\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "event: message_start\ndata: {\"message\":{\"content\":[],\"id\":\"msg_6a9ef3e5a96d9a155dd4463b\",\"model\":\"muse-spark-1.3\",\"role\":\"assistant\",\"stop_reason\":null,\"stop_sequence\":null,\"type\":\"message\",\"usage\":{\"input_tokens\":0,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"data\":\"Q-PaDgGyJ4hIKk41uslnSV0PGvrTkwJ5D-t5skfjtkIt-ABXsehMcLKJJ8RJHRKW2-XhkpPrex0aOkIdqWl99vpCgtOHIFFaSc4b3oGxA8XDx4T_2aKANfYrR1DYwYzGe6ZZ-DQnU0bnpVUzCcXkghkLdyTcJr2p8cvVo1rymFpB0wZsbQRBhxCMR6PrY4i0aOTe8_waq_Po1a4l3YzKnzZIIvYP090o5kltv7MAwqChXjQeTJlKq7ECFa6HVeoTa43oiLmoD2Bzih0VfvBvQfbSh2uDypRM7-f65sp35-VuZfwsM33ZTvefzDbd8zd0D50I6wcybsj8gulDhWWpY7cxoN1Xasy-ImvisACGeppIN66maIFq5dMT2s8_CQ8a2EQ0hw9kwJaIiygZCdRofs-T1yaGWvnQxL6MpNh6SXux9T1ZfuexNrXOhz5cR3_s1euhq3-hI3GDCUDLAtkjiigSE3w6PExrwHTHnvkuAOTP0Lw7DH4NtKv-RFFeZaQOk_0Bi2EV43tR0Hq_chtEDiSpoezDjexPWfdZvhRIaO6PwyJaQ8jVOaLUr6bh6jQ3IM19lv6R7lY4Neno8fLxMwTbf7v7q02lfhOaZe3jR1fqOFUq8e4VXV76_rJBgzThqPNqO8OI_betgX5d2KMcwWAkaZjmrP3rxA7g5xp_Q7bkFiBaaTd3HbYIk_cKbtLOS6TIIphb4SX6ADA2qJ0zoo98P2NOceZODT2WrLjKCHl5dT6b9l2feH9m21pW576ULfKhVSMzuy0cmmWI7rv2P4q-2Fw2klsDVJAc6q22bFjfDgzhybKuhuM_p1SYb8aswNrgggV-cqHWDpF1FdVpL5fHMO74l1uD8Sj_9wuuD0asMQustuvsnYq2EdI0lLvONFMApWCU0s3QA8_P0Iyf0YoCZOm5QGnA7l3O5nAPkFF4kxLSsgRe9zuP6A4oKBDLN8EHwZ3pB4WZGWpUT12cJxplmT3_n8xKaMgaz13qs27uvUm3wMI1seyfpkMwUPHmt1ftpk9f_1OAg3fvQgIkWPTp6K9NzVzEry6SP7zbPfls8yn529Qrbki7ZM_oz3xEvDg367ZCe97eVnjiqRrYsYM2MmDsQq9lUwkgXb84kpK6a4pEzX9NDvhTqhw7RI6RL9E-2Ki4ciUfHv5LVTZ0rNweCo6SYXC11f8FCeObIW_Esr2mqYpORFS4SDMU-4I1HQfD9z6cPjL22APpv9a7Pp1phI8x_aQZkPU7LkyBWyVNDeusI7wq_LC0UgJ0vCAguKcAvN7gxwpCo5MEEZivRuYBldEMwRo4he2WQQke9KEokqREdlPF3Lvaav0fqPfXqhJyHAq8bYBHWdzMjDJ_dJwhdbLQ-iNLmrK5JNg2vPDglWSznBXbrSX4iyMFk1Ni2Y1SQUIOOxS_InIwCg\",\"type\":\"redacted_thinking\"},\"index\":0,\"type\":\"content_block_start\"}\n\nevent: content_block_stop\ndata: {\"index\":0,\"type\":\"content_block_stop\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"text\":\"\",\"type\":\"text\"},\"index\":1,\"type\":\"content_block_start\"}\n\nevent: content_block_delta\ndata: {\"delta\":{\"text\":\"I'll look up the current weather in Paris.\",\"type\":\"text_delta\"},\"index\":1,\"type\":\"content_block_delta\"}\n\nevent: content_block_stop\ndata: {\"index\":1,\"type\":\"content_block_stop\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"id\":\"call_01a07ce8bb767c109c12f0a202e2ac19\",\"input\":{},\"name\":\"lookup_weather\",\"type\":\"tool_use\"},\"index\":2,\"type\":\"content_block_start\"}\n\nevent: content_block_delta\ndata: {\"delta\":{\"partial_json\":\"{\\\"city\\\":\\\"Paris\\\"}\",\"type\":\"input_json_delta\"},\"index\":2,\"type\":\"content_block_delta\"}\n\nevent: content_block_stop\ndata: {\"index\":2,\"type\":\"content_block_stop\"}\n\nevent: message_delta\ndata: {\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null},\"type\":\"message_delta\",\"usage\":{\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":113,\"input_tokens\":451,\"output_tokens\":155,\"output_tokens_details\":{\"thinking_tokens\":87}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Look up the current weather in Paris using lookup_weather. After receiving the result, report Paris's weather in one short sentence.\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"redacted_thinking\",\"data\":\"Q-PaDgGyJ4hIKk41uslnSV0PGvrTkwJ5D-t5skfjtkIt-ABXsehMcLKJJ8RJHRKW2-XhkpPrex0aOkIdqWl99vpCgtOHIFFaSc4b3oGxA8XDx4T_2aKANfYrR1DYwYzGe6ZZ-DQnU0bnpVUzCcXkghkLdyTcJr2p8cvVo1rymFpB0wZsbQRBhxCMR6PrY4i0aOTe8_waq_Po1a4l3YzKnzZIIvYP090o5kltv7MAwqChXjQeTJlKq7ECFa6HVeoTa43oiLmoD2Bzih0VfvBvQfbSh2uDypRM7-f65sp35-VuZfwsM33ZTvefzDbd8zd0D50I6wcybsj8gulDhWWpY7cxoN1Xasy-ImvisACGeppIN66maIFq5dMT2s8_CQ8a2EQ0hw9kwJaIiygZCdRofs-T1yaGWvnQxL6MpNh6SXux9T1ZfuexNrXOhz5cR3_s1euhq3-hI3GDCUDLAtkjiigSE3w6PExrwHTHnvkuAOTP0Lw7DH4NtKv-RFFeZaQOk_0Bi2EV43tR0Hq_chtEDiSpoezDjexPWfdZvhRIaO6PwyJaQ8jVOaLUr6bh6jQ3IM19lv6R7lY4Neno8fLxMwTbf7v7q02lfhOaZe3jR1fqOFUq8e4VXV76_rJBgzThqPNqO8OI_betgX5d2KMcwWAkaZjmrP3rxA7g5xp_Q7bkFiBaaTd3HbYIk_cKbtLOS6TIIphb4SX6ADA2qJ0zoo98P2NOceZODT2WrLjKCHl5dT6b9l2feH9m21pW576ULfKhVSMzuy0cmmWI7rv2P4q-2Fw2klsDVJAc6q22bFjfDgzhybKuhuM_p1SYb8aswNrgggV-cqHWDpF1FdVpL5fHMO74l1uD8Sj_9wuuD0asMQustuvsnYq2EdI0lLvONFMApWCU0s3QA8_P0Iyf0YoCZOm5QGnA7l3O5nAPkFF4kxLSsgRe9zuP6A4oKBDLN8EHwZ3pB4WZGWpUT12cJxplmT3_n8xKaMgaz13qs27uvUm3wMI1seyfpkMwUPHmt1ftpk9f_1OAg3fvQgIkWPTp6K9NzVzEry6SP7zbPfls8yn529Qrbki7ZM_oz3xEvDg367ZCe97eVnjiqRrYsYM2MmDsQq9lUwkgXb84kpK6a4pEzX9NDvhTqhw7RI6RL9E-2Ki4ciUfHv5LVTZ0rNweCo6SYXC11f8FCeObIW_Esr2mqYpORFS4SDMU-4I1HQfD9z6cPjL22APpv9a7Pp1phI8x_aQZkPU7LkyBWyVNDeusI7wq_LC0UgJ0vCAguKcAvN7gxwpCo5MEEZivRuYBldEMwRo4he2WQQke9KEokqREdlPF3Lvaav0fqPfXqhJyHAq8bYBHWdzMjDJ_dJwhdbLQ-iNLmrK5JNg2vPDglWSznBXbrSX4iyMFk1Ni2Y1SQUIOOxS_InIwCg\"},{\"type\":\"text\",\"text\":\"I'll look up the current weather in Paris.\"},{\"type\":\"tool_use\",\"id\":\"call_01a07ce8bb767c109c12f0a202e2ac19\",\"name\":\"lookup_weather\",\"input\":{\"city\":\"Paris\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"call_01a07ce8bb767c109c12f0a202e2ac19\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\"}\"}]}],\"tools\":[{\"name\":\"lookup_weather\",\"description\":\"Look up current weather\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"tool_choice\":{\"type\":\"none\"},\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"adaptive\",\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "event: message_start\ndata: {\"message\":{\"content\":[],\"id\":\"msg_6a9ef3e77f6ef36fdd8f4925\",\"model\":\"muse-spark-1.3\",\"role\":\"assistant\",\"stop_reason\":null,\"stop_sequence\":null,\"type\":\"message\",\"usage\":{\"input_tokens\":0,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"data\":\"Q-PaDgH-GUZkiqnb_seFaP70it_GxAEIUjt2BLllpyzfckiBOUVwr72xGQsSnj0y9aOhoaQ24A_HB0MHh4XWyYziVdRgq6lDPLHkJlmMzbusZxUZUx7D7B8kD8HrBbiTeIFbFlHaM9MiZW_PpYY9fpkdc7aeuX0mORdK8XTT3JPjvW1HPp4Qqp_iEOl9G2iM2Y6xno8SqeTcbc4EQs1LePaKrq86dBPmXBjkgQbYfvIw57o0SBEyelcudtnzJnaluOyOQdV2Ytk_r_xrYSCoBwgbf2KBCOFjdeQyruuwVZZ32JJdVOpWLw8eopUwAo2xYOZP0g8S8hTmOFvDKuzXipm1OoAXwD8Swm6ED3IIo0hHA5xSfMygDCee47nd-EpShNPamkCKodfX1QvePEJsIQK2iTgkh8IGUeEEne5dxgLuXvEAbeqGDvEy6T7IoSgZnc-KPtH6SoWM_kgc_eF_oN73Nxg2prMyCqTUNg5Qs2WLPjA9wSLmmnCoiDr1bYNIuQyn6adgv0-nZZXETJoRAHJBj65Asa8kbLyCYesb192178xCj2aBSdwyj-jc0i328sa9STS7mUSI7KvOt0Yi3kllLs1aSnHW-ogsUJUM7tTf83VO9fRRU_aW4H6qr4OAr8jbBKSD3bxE0AekDd24ZS-8YIjiP1tMOAPZ2JGQrNqbxnaeqhCZzD2nl-E8TMXaAJIk4L3oZtV80xJiUW7mLEf_jQBPAWhph4ujbkDaufGvNrl4FXyBJ-XpKMIA5z0jAYU2b3ul5-Qn5Km7Oc8fPu1M_0XGAOKG-pF9ppr4y-an4B4mKDYoAiSHp3cjv-fW57D87wBfQPaJRjOzWhzqENO-5MlB0CEsnDtrLveE2ui8wSECszParUNk5SEaeSYvrXLY0QkaE7CTK9ljwTKjJM34r1a8o16KFxGuzMrloNlxIYIaO_suM9CDuf8wNwe8KkAV2Kt28g9LwkL5b3mtI7ZSjTdHYOOhFFMzs-sjXboQoGAJGOhMnCQNc3E5-goSzvOzEIXehSTs8Mzyq8d3C2k5F9PSgdO6ZouHVURDgbEXTqWqoqjQ8huk5AbrUX16QwhJambd6P-3i--Idz9CbIVuWIqiMm80TvYlMttobYxTyFDIJrKJTrVA0PkAOFvOMfYt27d7z2ZMrTf6Vtm_DNniuhVbvVU3ZcWbb4LO-XkwH3bfHB-sckpwURP34KzOMvogaqWaMAZlabdd-lQKCc7tMhmo9BrYwHHvLxAELD2qQnyym43Yo1iJNAyrqoUAFk6K029fDon7h9ybmKZdBxRtS3arUesY_91Xep0tA0Y1UYUKo8zKjK3Op_DbVyC5OoR8rxpumKiqk2PtG7RPf-WjEXGkleGqZtxix7ILSkQXxW6McY1r_6LAh2Nv0nUzqS019tTb7mx2OLyt2g\",\"type\":\"redacted_thinking\"},\"index\":0,\"type\":\"content_block_start\"}\n\nevent: content_block_stop\ndata: {\"index\":0,\"type\":\"content_block_stop\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"text\":\"\",\"type\":\"text\"},\"index\":1,\"type\":\"content_block_start\"}\n\nevent: content_block_delta\ndata: {\"delta\":{\"text\":\"It's sunny in Paris.\",\"type\":\"text_delta\"},\"index\":1,\"type\":\"content_block_delta\"}\n\nevent: content_block_stop\ndata: {\"index\":1,\"type\":\"content_block_stop\"}\n\nevent: message_delta\ndata: {\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"type\":\"message_delta\",\"usage\":{\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"input_tokens\":210,\"output_tokens\":50,\"output_tokens_details\":{\"thinking_tokens\":35}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -0,0 +1,29 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": ["prefix:meta-messages", "provider:meta", "protocol:meta-messages", "text", "reasoning", "adaptive"],
"name": "meta-messages/streams-text-with-adaptive-thinking",
"recordedAt": "2026-09-07T17:26:18.565Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"adaptive\",\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "event: message_start\ndata: {\"message\":{\"content\":[],\"id\":\"msg_6a9ef3b8e46a777cfdc94a93\",\"model\":\"muse-spark-1.3\",\"role\":\"assistant\",\"stop_reason\":null,\"stop_sequence\":null,\"type\":\"message\",\"usage\":{\"input_tokens\":0,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"data\":\"Q-PaDgFtwwNGqSqKxPk9Zj-j9xogYigpDsDHrLKPV-DxmXcbhtXDPrGPuj5qB4l1FslhuEQFCA8zIRyNGaCmzM0JG4GOZe3LpFYM2iegBbi24l4V7souryAwUs_7PrInxObuguMwh2S66ML6PwlfVRVTAwPsgDSCl6Y9tkoDHr2KBRY2fzJhmjkotuFRK2LYvNp9pKi89JxnoIKK8B_UJc_B2r4vU_Txq9_rWTmo2eXFoGOSMZNaHpVrvACRQKC1jc6-8Pj5JkJN11_Cs2fHqlaCwOoPDVJxHXUXP1AMHU1mG0jR4qf9wUaz8kMmyCBBijlfQJ4G7nsQs7LJO9gS2ropmynDoO5IEIAFf7ekxLw54Fx3JigC0_qz4Jx4a04Ey8JBzWRlZNdqggpq2_I_VoT3S0zhCYDp8ZAJQs2JqvvF-ZmLcMPH1PmpkgRySqXl5TDxERIpdL3aOUzTl-PTHVMIGEcSAz84fFwU4p1kS5llGEGC6jHlClscEEto1Y-qthL6BoeDALzk7GHEMVQD0bfC4E5yGCh0_xlWqsG3XPj0wpYKBc0hfh7vjO1NpzPxCtaDjnRwR6TzSo6x_HrEL3VELArHhpYAXl30fNO-EepduZ3Hd_kWqWj_GhdIM4dRh2zoytbhIesVD7OvBqU47urKFnpXw4kswJ-NoVVNk00ysMMHwobT9BdJL9UCoXlNzyErPe8LkA9f0VrKkuLCg5FDr_DCEYXHXr7fdRmC3JTKuzvxffvPYppUt3hs5gbdXWgT2iZzunt4bSfFne7BPkj9OGREyfLcukvGAh_oqPLXuu0lert9wbTAqRUTyM5Y1nSjw3h2B3jv91mH78mGvhy8LeVNLkrSMxEa4GtrrUvlzkPnZayc_LE2Sr7JnQjhwylVNWvkcIAjstOZShjPKe4pUJwj-cUXmXZB7Zwr-Q9WLtprFgQoBvadd5DHD4aynsYEabKZVcOqhV8l2zyaUCGobUzdPqBC5kk5-VPmNTqm6Qs91mb1y_5ydmHjTWmSW6qtN_op-Xfwqr_hllvOjhnAvvDJWUOKH-YIgZw_k_db2mzW1hO093jSDMYv6wpiy_5XWMNJBjcH8hvR_pFjXIqScUnKPranEZvCyGfHR-UBNJ89s_JEE49chcIt8LiukBeXQ_TOe29kzWnT1tYtfGQXP8FY2Wukw5yhvP3coGp1-LPZRuw3BLP2tWmiijmyj5lMikp8MIeqOztN8zgsArZ9jq21EtDSNfpO_0C4GEOtCZQYvZHRpDeY966XDnN7Blenn_8FZ95M-M-1My8hsmF__JrnY4ZFZr2274f_FbyJaSqGj6H-YRuzawO2TVLWHxAr6v2LCiB_QX01PwUI1yaJmP0mI-Qz5zXgSHzfVzhF7l1zEnM8qIYcSXzD1Gp3rjpn2sq-rR-JLqeHq080Hl0X9c8lzozw3qCpvcTfA-4ZTdXU9ax3nt7emcPVFo9N_JBjYashC7KvjkIWjMhQoTbIRlO18gjEa-Dvtsmo4_xE1ApYRoylbhxg3_fQJjKLfCBv32V6BM8qWKINL7d1nYP7PtAKnglCdnT_dK0_pCK6ejGfmdWZgVj0DhVZUCXf2kN530Y0hoCb1jBgwJ16a5gUqJMtnWScarIGSVrsYMA_uEt7DwQzGYAGESVIgi8JVATkxoq4EbyOLCHX7MIKmAPGV2WWTjWAWLnE2ITjlXtBkUAwjq2v957BMgIqxSNxEjG75Gd0i1XjZAujCSCXMrywZ72-VdQy4cD6kzH9mv04fZ7_dqn9eKPHBY7_T7V6myRDj_ImkffXcygmd7F-_-kHMExI6ABas1WtfDjkYgbRM0jMtIHYL-W9xT8vCSLSdvHcb5Y8XSXT5GTwI7eIlBHnCvFrgunbCo9bJJlT2Gc1RcgeZ4rg3yu1MNF4Mfk6DBwDhduGh5l7nypL00B0HokLfF4H7N1oSKL9jdVE3oMyJuA9NV1SJiyqcneSVHBzqjFZ5sszFXPlBKEz31izxFToAF4tDYMR507zYLcxyLA4AmHlkmn0qeozPb5qk-qZB6yLywtewv8O1KqxieAEJnEmyiqGnoKBdkjpeYmplErSxJyzoRX4fXb9mOdb-izJgvqSQ5uLm0_EXf3OklpMoAJ2CPJ6cxpg_5bhiwbgEtafCuDyXVL9Zzi872yUb_Z-ATlqU1M2Ai67f1piHIUDzMn4kulXQhPMb_R4eznMbX_aRigQh92-t1USoFtX2vmyCUQageFcrHHK524gPQ7rkOfDvZ8GHPXi3YZFnzGkSKjQD0jumFtzDiRqYkkMECep0zgY8MjuWLMjlMuigpkzSm8GPZ2aFeIMxJCYR8iMf7fH9NyHAJnicx4wdW_UqqgY5jfVtDvPSL0SRScyyK0jNdmofDHjWp4Tehih5OOw81E3BYLCiIv2p2zTMRSTZxFyG3HI91tIEaJu_RnIMFxzCilaP1LcbMrjyFzBtLJiXXJ--eFYyIMYKgfbWIOTsjLLJGC8NS2wuzaXB-C6GLsuSEHci5a8f5KGqaXK99LvHtY4k36WhEoVUjQEGY7HNJrtdjBUlqDgnT9iO06gGd2QCGvlP4us-Z3q2Bcyhf0LzOgn_Bpjc6YBk4TNF1AMxG15qmhnsrWcGElkpk3Vh9npC8--vkvhh7pOPfTZlNjQQh900OemY9gwxHyCBbt1096g4qkYOntGJmw-wVjyWxOt9K9CaM9WxY3wFyOmwqiFL1PK4CMeKMZk54iZ1ZiyXDOvsO7qUa9XQGDy3W8ic6R3DUzdkxEEBFYHAjDy3qE\",\"type\":\"redacted_thinking\"},\"index\":0,\"type\":\"content_block_start\"}\n\nevent: content_block_stop\ndata: {\"index\":0,\"type\":\"content_block_stop\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"text\":\"\",\"type\":\"text\"},\"index\":1,\"type\":\"content_block_start\"}\n\nevent: content_block_delta\ndata: {\"delta\":{\"text\":\"37887\",\"type\":\"text_delta\"},\"index\":1,\"type\":\"content_block_delta\"}\n\nevent: content_block_stop\ndata: {\"index\":1,\"type\":\"content_block_stop\"}\n\nevent: message_delta\ndata: {\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"type\":\"message_delta\",\"usage\":{\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"input_tokens\":23,\"output_tokens\":266,\"output_tokens_details\":{\"thinking_tokens\":254}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -0,0 +1,29 @@
{
"version": 1,
"metadata": {
"model": "muse-spark-1.3",
"tags": ["prefix:meta-messages", "provider:meta", "protocol:meta-messages", "text", "reasoning", "enabled"],
"name": "meta-messages/streams-text-with-enabled-thinking",
"recordedAt": "2026-09-07T17:26:20.464Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.meta.ai/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"muse-spark-1.3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"enabled\",\"budget_tokens\":1024,\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream"
},
"body": "event: message_start\ndata: {\"message\":{\"content\":[],\"id\":\"msg_6a9ef3bbb7e691b3ea26427b\",\"model\":\"muse-spark-1.3\",\"role\":\"assistant\",\"stop_reason\":null,\"stop_sequence\":null,\"type\":\"message\",\"usage\":{\"input_tokens\":0,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"data\":\"Q-PaDgEpowvGua1C8z2RSICyyeSLruZ1JBUhrhLhgqf_XauiaESRHyHgIPVF0Cfxp7eLB_ESNObq7vJNDLdffE-RaN5nEauXhRxH6fJErC-4H_KELZFGF-qNnGu2DWl-gEojkwwkq1HtTtT3rOEk5isGjdribnDvR7a1zkpG3I0BxE_UyseISDSbkev5tyV7DkiH4xyv2ZmwuO3HHGgmAztEek9vm0ZTElZ8PIPvHG6P38aYGTGKrTjyOhmN3LBa_NFvtQQkUMKRB3n7L_N5KB1NbYkPzLEJ6L5dL9DJZBSsZms0O62QZ2zs2oWZ-ekQWL4oIuiRozWVpmwzjtkJl9WXtyvdL-4MACucXMSN9V7hWzC50g-DgdeFPSslsA9gy619wTNPPIxPymGWeD9LYKR3iwTRLNYzuycHakNqikhiwz9MweMod5GLTdbtypZJcV7ourz58jmK4YKsqleVPVvXM6bIoNuT2yGj23-tAYxQszGXQMHxSTnaLAHl6sUdiwTqX4wbFYfX-ogkIZsrca9mv5E2gkH1wJl19GPCxFaSDCHxh3QAW0RIExKUJly9Ydpoegmojfo0F4Hjt13zDm03fLEQ0eY8GjxIIHDt_zVRQGTW8d4tgZpTt4K9H1TTefG34EerN7jl7L-WNUe_gKorN4aSYBS6S6ma4Vb91NlUQ4cEGBKVQvumYe16p9Xj6Te-tCuiBmTOCq6iDsTH-eMlip_Bc18MD2BCGr2NdF4PB2vqHGSPIvSHDRXBjoPNtm18opAsUnOBu7-tysb8-EKFd5z7JbWgegQWNs11FdX0uFtC-U9ROwbHLJ3IHk4m2UHc2USwi7v4Xe6FtnkJ0VCZTajFUTXW33lwO3UsHuqEbxVnU_Rzuj6Ryjb08bpXpJBjIEGf2wBS3_I78cuXKOChHu-evtnjezc77joRUP8_I8pWsQbQ6bHABD95p8q6ZdQpDr9XQALbVg3ZvD5_QgF59rIxkW9a2L78oQpu0O3UT7-jxNFe38-OXuEr3VTv7RzISu1zjvBZD0k9-2q-T9BAJmsK6oPajJQWKwHBYF3w6vbsQ8tRDdh2uerhlAIWU0YL92gPCmT3mR6HxSGjAx08t-gib_YrzvWn0oTfLNk_BNfAMpG7Mj2GYNbyTkd_tjNZ2fOwVKbygsoFIUEvh2B-xyHOYuhXfxzI8-Gt55FMGDLLKcHfgmOZAUz1xVkIdm6bcuRFuQppgTzYIr66GN6tsr9KxTN2GZ2NHCwexe0hSAGfKVyLr8QBxTDRlzubBqe-osgQJrJtS7uM1MuWK_8grkZrNsn6g3ami0JboEmM_Ct3gVhkXo6lhCCcjn1gnGX-HOLczT3Xty1wYkeayUpi9x326nrSmypxzbzCRS3bOaPvSoBCszri0uTfUnVQK5kTVuRD30fGxauPhA\",\"type\":\"redacted_thinking\"},\"index\":0,\"type\":\"content_block_start\"}\n\nevent: content_block_stop\ndata: {\"index\":0,\"type\":\"content_block_stop\"}\n\nevent: content_block_start\ndata: {\"content_block\":{\"text\":\"\",\"type\":\"text\"},\"index\":1,\"type\":\"content_block_start\"}\n\nevent: content_block_delta\ndata: {\"delta\":{\"text\":\"37887\",\"type\":\"text_delta\"},\"index\":1,\"type\":\"content_block_delta\"}\n\nevent: content_block_stop\ndata: {\"index\":1,\"type\":\"content_block_stop\"}\n\nevent: message_delta\ndata: {\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"type\":\"message_delta\",\"usage\":{\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"input_tokens\":23,\"output_tokens\":143,\"output_tokens_details\":{\"thinking_tokens\":131}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
{
"version": 1,
"metadata": {
"model": "MiniMax-M3",
"tags": [
"prefix:minimax-chat",
"provider:minimax",
"protocol:minimax-chat",
"text",
"usage",
"thinking-off"
],
"name": "minimax-chat/m3-streams-text-with-thinking-disabled",
"recordedAt": "2026-09-07T16:53:20.976Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/v1/chat/completions",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M3\",\"messages\":[{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"max_completion_tokens\":1536,\"thinking\":{\"type\":\"disabled\"},\"reasoning_split\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "data: {\"id\":\"d8d409a572a67df400527c6592b480df\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\"}}],\"created\":1788800000,\"model\":\"MiniMax-M3\",\"object\":\"chat.completion.chunk\",\"usage\":null,\"service_tier\":\"standard\"}\n\ndata: {\"id\":\"d8d409a572a67df400527c6592b480df\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"378\",\"role\":\"assistant\"}}],\"created\":1788800000,\"model\":\"MiniMax-M3\",\"object\":\"chat.completion.chunk\",\"usage\":null,\"service_tier\":\"standard\"}\n\ndata: {\"id\":\"d8d409a572a67df400527c6592b480df\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"87\",\"role\":\"assistant\"}}],\"created\":1788800000,\"model\":\"MiniMax-M3\",\"object\":\"chat.completion.chunk\",\"usage\":null,\"service_tier\":\"standard\"}\n\ndata: {\"id\":\"d8d409a572a67df400527c6592b480df\",\"choices\":[{\"finish_reason\":\"stop\",\"index\":0,\"delta\":{\"role\":\"assistant\"}}],\"created\":1788800000,\"model\":\"MiniMax-M3\",\"object\":\"chat.completion.chunk\",\"usage\":null,\"service_tier\":\"standard\"}\n\ndata: {\"id\":\"d8d409a572a67df400527c6592b480df\",\"choices\":[],\"created\":1788800000,\"model\":\"MiniMax-M3\",\"object\":\"chat.completion.chunk\",\"usage\":{\"total_tokens\":182,\"total_characters\":0,\"prompt_tokens\":179,\"completion_tokens\":3,\"prompt_tokens_details\":{\"cached_tokens\":128}},\"service_tier\":\"standard\",\"base_resp\":{\"status_code\":0,\"status_msg\":\"\"}}\n\n"
}
}
]
}
@@ -0,0 +1,36 @@
{
"version": 1,
"metadata": {
"model": "MiniMax-M2.7",
"tags": [
"prefix:minimax-messages",
"provider:minimax",
"protocol:anthropic-messages",
"text",
"usage",
"reasoning"
],
"name": "minimax-messages/m2-7-streams-default-thinking",
"recordedAt": "2026-09-07T16:53:20.721Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/anthropic/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M2.7\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":1536}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"bd0c49bb4f5e6f26b77bab9b5a917484\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M2.7\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":57,\"output_tokens\":0}}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"The user\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" asks: \\\"What is 173 multiplied by 219? Reply with only the final integer.\\\" So we compute 173 * 219. Compute:\\n\\n173 * 219 = \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173 * (200 + 19) = 173*200 + 173*19 = 34600 + (173*19). 173*19 = 173*20\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" - 173 = 3460 - 173 = 3287. So total = 34600 + 3287 = 37887.\\n\\nAlternatively compute directly: 219 * \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173 = 219 * 173 = (219 * 100) + (219 * 70) + (219 * 3) = 21900 + 15330 + 657\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" = 37887.\\n\\nThus answer is 37887. We must reply with only the final integer: \\\"37887\\\". No extra text.\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"1625da2f905565076ee90b9c2919db39b1719731c0820eca2f2a1c9c28324e87\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"37887\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":57,\"output_tokens\":187,\"output_tokens_details\":{\"thinking_tokens\":184}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -0,0 +1,56 @@
{
"version": 1,
"metadata": {
"model": "MiniMax-M3",
"tags": [
"prefix:minimax-messages",
"provider:minimax",
"protocol:anthropic-messages",
"tool",
"tool-loop",
"reasoning",
"continuation",
"usage"
],
"name": "minimax-messages/m3-continues-a-tool-loop-with-adaptive-thinking",
"recordedAt": "2026-09-07T16:53:25.185Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/anthropic/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Look up the current weather in Paris using get_weather before answering. After receiving the result, report the weather in one short sentence.\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"tool_choice\":{\"type\":\"auto\"},\"stream\":true,\"max_tokens\":1536,\"thinking\":{\"type\":\"adaptive\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"6a532037868639a6189e7c0b9e1c9aa2\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"The user wants me\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" to look up the\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" current weather in Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" using the get_\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"weather tool, then\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" report it\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" in one short sentence\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\". Let\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" me call the tool\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\".\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"f05cb5f4873950f23d194289c41d79b96ebd37c9411a45b306ed44f135a910d4\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"call_01a07cc9f2d47d83a6424ff3\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"input_tokens\":239,\"output_tokens\":63,\"cache_read_input_tokens\":203,\"service_tier\":\"standard\",\"output_tokens_details\":{\"thinking_tokens\":33}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
},
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/anthropic/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Look up the current weather in Paris using get_weather before answering. After receiving the result, report the weather in one short sentence.\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"The user wants me to look up the current weather in Paris using the get_weather tool, then report it in one short sentence. Let me call the tool.\",\"signature\":\"f05cb5f4873950f23d194289c41d79b96ebd37c9411a45b306ed44f135a910d4\"},{\"type\":\"tool_use\",\"id\":\"call_01a07cc9f2d47d83a6424ff3\",\"name\":\"get_weather\",\"input\":{\"city\":\"Paris\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"call_01a07cc9f2d47d83a6424ff3\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperature\\\":\\\"18C\\\"}\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"tool_choice\":{\"type\":\"none\"},\"stream\":true,\"max_tokens\":1536,\"thinking\":{\"type\":\"adaptive\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"e9e7ea89f63eca1cd17a037aef28eed1\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"The weather\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" in Paris is sunny\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" with\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" a\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" temperature of 18\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"°C.\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":164,\"output_tokens\":16,\"cache_read_input_tokens\":128,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -0,0 +1,36 @@
{
"version": 1,
"metadata": {
"model": "MiniMax-M3",
"tags": [
"prefix:minimax-messages",
"provider:minimax",
"protocol:anthropic-messages",
"tool",
"thinking-off",
"usage"
],
"name": "minimax-messages/m3-generates-a-named-tool-call-with-default-thinking-off",
"recordedAt": "2026-09-07T16:53:24.366Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/anthropic/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Use get_weather to look up the current weather in Paris.\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get the current weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false}}],\"tool_choice\":{\"type\":\"tool\",\"name\":\"get_weather\"},\"stream\":true,\"max_tokens\":512}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"a60a8adb2eff7545f809b9df2689f1b8\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"I'll\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" look\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" up the current weather\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" in Paris for you\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\".\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"tool_use\",\"id\":\"call_b0246853f3c4432ea455cc99\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"city\\\":\\\"Paris\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\"},\"usage\":{\"input_tokens\":401,\"output_tokens\":39,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -0,0 +1,36 @@
{
"version": 1,
"metadata": {
"model": "MiniMax-M3",
"tags": [
"prefix:minimax-messages",
"provider:minimax",
"protocol:anthropic-messages",
"text",
"usage",
"reasoning"
],
"name": "minimax-messages/m3-streams-adaptive-thinking",
"recordedAt": "2026-09-07T16:53:16.470Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/anthropic/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":1536,\"thinking\":{\"type\":\"adaptive\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"ece24e3da050a8b1d7e8b1ad924a9e17\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" × 219\\n\\n\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173 × 200\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" = 346\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"00\\n173 ×\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 19 = \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"173 × 20\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" - 173 =\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 3460\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" - 173 =\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 3287\\n\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"34600 + \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"3287 = \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"37887\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"af0089d30b3ee7aff92a96e68064f4ed9346ca97de4f579555d2b3e1e61b53ea\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"37887\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":64,\"output_tokens\":54,\"cache_read_input_tokens\":128,\"service_tier\":\"standard\",\"output_tokens_details\":{\"thinking_tokens\":49}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
@@ -0,0 +1,36 @@
{
"version": 1,
"metadata": {
"model": "MiniMax-M3",
"tags": [
"prefix:minimax-messages",
"provider:minimax",
"protocol:anthropic-messages",
"text",
"usage",
"thinking-off"
],
"name": "minimax-messages/m3-streams-text-with-thinking-disabled",
"recordedAt": "2026-09-07T16:53:16.028Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/anthropic/v1/messages",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":1536,\"thinking\":{\"type\":\"disabled\"}}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"01664c047875a0f573c966467c8cccc1\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"service_tier\":\"standard\"},\"service_tier\":\"standard\"}}\n\nevent: ping\ndata: {\"type\":\"ping\"}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"378\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"87\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"input_tokens\":51,\"output_tokens\":3,\"cache_read_input_tokens\":128,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
}
}
]
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,36 @@
{
"version": 1,
"metadata": {
"model": "MiniMax-M3",
"tags": [
"prefix:minimax-responses",
"provider:minimax",
"protocol:open-responses",
"text",
"usage",
"thinking-off"
],
"name": "minimax-responses/m3-streams-text-with-effort-none",
"recordedAt": "2026-09-07T16:53:21.223Z"
},
"interactions": [
{
"transport": "http",
"request": {
"method": "POST",
"url": "https://api.minimax.io/v1/responses",
"headers": {
"content-type": "application/json"
},
"body": "{\"model\":\"MiniMax-M3\",\"input\":[{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"reasoning\":{\"effort\":\"none\"},\"max_output_tokens\":1536,\"stream\":true}"
},
"response": {
"status": 200,
"headers": {
"content-type": "text/event-stream; charset=utf-8"
},
"body": "event: response.created\ndata: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"40d878e9c7ecc67905d069cdf9bec7c0\",\"object\":\"response\",\"created_at\":1788800001,\"model\":\"MiniMax-M3\",\"status\":\"in_progress\",\"output\":[],\"output_text\":null,\"usage\":null,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"metadata\":{},\"tools\":null,\"tool_choice\":\"auto\",\"temperature\":1,\"top_p\":0.95,\"text\":{\"format\":{\"type\":\"text\"}},\"reasoning\":{\"effort\":\"none\",\"summary\":null},\"max_output_tokens\":1536,\"parallel_tool_calls\":true,\"previous_response_id\":null,\"conversation\":null,\"store\":false,\"service_tier\":\"standard\",\"safety_identifier\":null,\"truncation\":\"disabled\"}}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"sequence_number\":1,\"response\":{\"id\":\"40d878e9c7ecc67905d069cdf9bec7c0\",\"object\":\"response\",\"created_at\":1788800001,\"model\":\"MiniMax-M3\",\"status\":\"in_progress\",\"output\":[],\"output_text\":null,\"usage\":null,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"metadata\":{},\"tools\":null,\"tool_choice\":\"auto\",\"temperature\":1,\"top_p\":0.95,\"text\":{\"format\":{\"type\":\"text\"}},\"reasoning\":{\"effort\":\"none\",\"summary\":null},\"max_output_tokens\":1536,\"parallel_tool_calls\":true,\"previous_response_id\":null,\"conversation\":null,\"store\":false,\"service_tier\":\"standard\",\"safety_identifier\":null,\"truncation\":\"disabled\"}}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"sequence_number\":2,\"item\":{\"id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\",\"type\":\"message\",\"status\":\"in_progress\",\"role\":\"assistant\",\"content\":[]},\"output_index\":0}\n\nevent: response.content_part.added\ndata: {\"type\":\"response.content_part.added\",\"sequence_number\":3,\"part\":{\"type\":\"output_text\",\"text\":\"\",\"annotations\":[]},\"output_index\":0,\"content_index\":0,\"item_id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\"}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":4,\"output_index\":0,\"content_index\":0,\"item_id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\",\"delta\":\"378\"}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"sequence_number\":5,\"output_index\":0,\"content_index\":0,\"item_id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\",\"delta\":\"87\"}\n\nevent: response.output_text.done\ndata: {\"type\":\"response.output_text.done\",\"sequence_number\":6,\"output_index\":0,\"content_index\":0,\"item_id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\",\"text\":\"37887\"}\n\nevent: response.content_part.done\ndata: {\"type\":\"response.content_part.done\",\"sequence_number\":7,\"part\":{\"type\":\"output_text\",\"text\":\"37887\",\"annotations\":[]},\"output_index\":0,\"content_index\":0,\"item_id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\"}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"sequence_number\":8,\"item\":{\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"37887\",\"annotations\":[]}],\"id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\",\"type\":\"message\"},\"output_index\":0}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"sequence_number\":9,\"response\":{\"id\":\"40d878e9c7ecc67905d069cdf9bec7c0\",\"object\":\"response\",\"created_at\":1788800001,\"model\":\"MiniMax-M3\",\"status\":\"completed\",\"output\":[{\"id\":\"40d878e9c7ecc67905d069cdf9bec7c0_msg\",\"type\":\"message\",\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"37887\",\"annotations\":null}]}],\"output_text\":\"37887\",\"usage\":{\"input_tokens\":179,\"output_tokens\":3,\"total_tokens\":182,\"input_tokens_details\":{\"cached_tokens\":128}},\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"metadata\":{},\"tools\":null,\"tool_choice\":\"auto\",\"temperature\":1,\"top_p\":0.95,\"text\":{\"format\":{\"type\":\"text\"}},\"reasoning\":{\"effort\":\"none\",\"summary\":null},\"max_output_tokens\":1536,\"parallel_tool_calls\":true,\"previous_response_id\":null,\"conversation\":null,\"store\":false,\"service_tier\":\"standard\",\"safety_identifier\":null,\"truncation\":\"disabled\"}}\n\n"
}
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More