mirror of
https://github.com/anomalyco/opencode.git
synced 2026-09-30 04:27:37 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b4a372c2a | ||
|
|
d12f89b9c8 |
@@ -662,7 +662,7 @@ jobs:
|
||||
needs:
|
||||
- version
|
||||
- publish
|
||||
if: ${{ !cancelled() && github.repository == 'anomalyco/opencode' && github.ref_name == 'v2' && needs.version.outputs.release && needs.publish.result == 'success' }}
|
||||
if: github.repository == 'anomalyco/opencode' && github.ref_name == 'v2' && needs.version.outputs.release && needs.publish.result == 'success'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
# Unlike dev, V2 publishes a tag rather than a GitHub Release event.
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"packages/ai": {
|
||||
"name": "@opencode/ai",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@aws-sdk/credential-providers": "3.1057.0",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -54,7 +54,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@corvu/drawer": "catalog:",
|
||||
"@dnd-kit/abstract": "0.5.0",
|
||||
@@ -112,7 +112,7 @@
|
||||
},
|
||||
"packages/cli": {
|
||||
"name": "@opencode/cli",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode.cjs",
|
||||
"opencode2": "./bin/opencode2.cjs",
|
||||
@@ -178,7 +178,7 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@opencode/client",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/protocol": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -204,7 +204,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode/codemode",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -217,7 +217,7 @@
|
||||
},
|
||||
"packages/console/app": {
|
||||
"name": "@opencode/console-app",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@ibm/plex": "6.4.1",
|
||||
@@ -253,7 +253,7 @@
|
||||
},
|
||||
"packages/console/core": {
|
||||
"name": "@opencode/console-core",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sts": "3.782.0",
|
||||
"@jsx-email/render": "1.1.1",
|
||||
@@ -280,7 +280,7 @@
|
||||
},
|
||||
"packages/console/function": {
|
||||
"name": "@opencode/console-function",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@opencode/console-core": "workspace:*",
|
||||
@@ -297,7 +297,7 @@
|
||||
},
|
||||
"packages/console/mail": {
|
||||
"name": "@opencode/console-mail",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
@@ -321,7 +321,7 @@
|
||||
},
|
||||
"packages/console/support": {
|
||||
"name": "@opencode/console-support",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@cloudflare/vite-plugin": "1.15.2",
|
||||
"@opencode/console-core": "workspace:*",
|
||||
@@ -341,7 +341,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode/core",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@ai-sdk/cohere": "3.0.27",
|
||||
"@ai-sdk/gateway": "3.0.104",
|
||||
@@ -377,7 +377,6 @@
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"ignore": "7.0.5",
|
||||
"immer": "11.1.4",
|
||||
"jose": "6.0.11",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"mime-types": "3.0.2",
|
||||
"tree-sitter-bash": "0.25.0",
|
||||
@@ -410,7 +409,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode/desktop",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@zip.js/zip.js": "2.7.62",
|
||||
"electron-context-menu": "5.0.0",
|
||||
@@ -459,7 +458,7 @@
|
||||
},
|
||||
"packages/enterprise": {
|
||||
"name": "@opencode/enterprise",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@hono/standard-validator": "catalog:",
|
||||
"@opencode-ai/sdk": "1.18.21",
|
||||
@@ -496,7 +495,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@opencode/function",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@octokit/auth-app": "8.0.1",
|
||||
"@octokit/rest": "catalog:",
|
||||
@@ -512,7 +511,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode/http-recorder",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@effect/platform-node-shared": "4.0.0-rc.112",
|
||||
},
|
||||
@@ -531,7 +530,7 @@
|
||||
},
|
||||
"packages/httpapi-codegen": {
|
||||
"name": "@opencode/httpapi-codegen",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"prettier": "3.6.2",
|
||||
@@ -544,7 +543,7 @@
|
||||
},
|
||||
"packages/latex": {
|
||||
"name": "@opencode/latex",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -558,7 +557,7 @@
|
||||
},
|
||||
"packages/merman": {
|
||||
"name": "@opencode/merman",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
@@ -573,7 +572,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@opencode/plugin",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@opencode/ai": "workspace:*",
|
||||
@@ -612,7 +611,7 @@
|
||||
},
|
||||
"packages/plugin-browser": {
|
||||
"name": "@opencode/plugin-browser",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/plugin": "workspace:*",
|
||||
"@opencode/schema": "workspace:*",
|
||||
@@ -642,7 +641,7 @@
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@opencode/protocol",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/schema": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -657,7 +656,7 @@
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@opencode/schema",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -681,7 +680,7 @@
|
||||
},
|
||||
"packages/sdk": {
|
||||
"name": "@opencode/sdk",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -702,7 +701,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode/server",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "catalog:",
|
||||
"@effect/platform-node-shared": "catalog:",
|
||||
@@ -724,7 +723,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode/session-ui",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@opencode/client": "workspace:*",
|
||||
@@ -759,7 +758,7 @@
|
||||
},
|
||||
"packages/simulation": {
|
||||
"name": "@opencode/simulation",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/ai": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -779,7 +778,7 @@
|
||||
},
|
||||
"packages/stats/app": {
|
||||
"name": "@opencode/stats-app",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@ibm/plex": "6.4.1",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -813,7 +812,7 @@
|
||||
},
|
||||
"packages/stats/core": {
|
||||
"name": "@opencode/stats-core",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-athena": "3.933.0",
|
||||
"@planetscale/database": "1.19.0",
|
||||
@@ -832,7 +831,7 @@
|
||||
},
|
||||
"packages/stats/server": {
|
||||
"name": "@opencode/stats-server",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-firehose": "3.933.0",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -878,7 +877,7 @@
|
||||
},
|
||||
"packages/theme": {
|
||||
"name": "@opencode/theme",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opentui/core": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -892,7 +891,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode/tui",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@opencode/client": "workspace:*",
|
||||
"@opencode/core": "workspace:*",
|
||||
@@ -926,7 +925,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode/ui",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "catalog:",
|
||||
"@pierre/diffs": "catalog:",
|
||||
@@ -961,7 +960,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode/util",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@effect/opentelemetry": "catalog:",
|
||||
"@effect/platform-node": "catalog:",
|
||||
@@ -999,7 +998,7 @@
|
||||
},
|
||||
"packages/web": {
|
||||
"name": "@opencode/web",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@astrojs/cloudflare": "12.6.3",
|
||||
"@astrojs/markdown-remark": "6.3.1",
|
||||
@@ -1040,7 +1039,7 @@
|
||||
},
|
||||
"services/update": {
|
||||
"name": "@opencode/update",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"jose": "6.0.11",
|
||||
"semver": "catalog:",
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "opencode",
|
||||
"description": "AI-powered development tool",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.4.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"name": "@opencode/ai",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -38,30 +38,13 @@ const resolve = (policy: CachePolicy | undefined): CachePolicyObject => {
|
||||
// prefix caching, Gemini's implicit + out-of-band CachedContent). Skip the
|
||||
// whole policy pass for these — emitting hints would be harmless but pointless.
|
||||
const RESPECTS_INLINE_HINTS = new Set([
|
||||
"alibaba-messages",
|
||||
"anthropic-messages",
|
||||
"anthropic-compatible-messages",
|
||||
"cloudflare-ai-gateway-messages",
|
||||
"google-vertex-messages",
|
||||
"meta-messages",
|
||||
"minimax-messages",
|
||||
"moonshot-messages",
|
||||
"zai-coding-messages",
|
||||
"bedrock-converse",
|
||||
"openrouter",
|
||||
])
|
||||
|
||||
// OpenRouter upstreams other than Anthropic and Alibaba Qwen cache without breakpoints. Gemini uses only the last
|
||||
// breakpoint, so a conversation-tail breakpoint writes a new cache every step and costs more than none. Qwen ignores
|
||||
// breakpoints on tool definitions and caches tools with the system prompt.
|
||||
const openRouterPolicy = (modelID: string): CachePolicyObject => {
|
||||
// `~anthropic/claude-sonnet-latest` style IDs are OpenRouter aliases for the latest model in a family.
|
||||
const id = modelID.replace(/^~/, "")
|
||||
if (id.startsWith("anthropic/")) return AUTO
|
||||
if (id.startsWith("qwen/")) return { system: true, messages: { tail: 1 } }
|
||||
return NONE
|
||||
}
|
||||
|
||||
const makeHint = (ttlSeconds: number | undefined): CacheHint =>
|
||||
ttlSeconds !== undefined ? new CacheHint({ type: "ephemeral", ttlSeconds }) : new CacheHint({ type: "ephemeral" })
|
||||
|
||||
@@ -167,10 +150,9 @@ const countHints = (request: LLMRequest) =>
|
||||
|
||||
export const applyCachePolicy = (request: LLMRequest): LLMRequest => {
|
||||
if (!RESPECTS_INLINE_HINTS.has(request.model.route.id)) return request
|
||||
const policy =
|
||||
request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto")
|
||||
? openRouterPolicy(request.model.id)
|
||||
: resolve(request.cache)
|
||||
if (request.model.route.id === "openrouter" && (request.cache === undefined || request.cache === "auto"))
|
||||
return request
|
||||
const policy = resolve(request.cache)
|
||||
if (!policy.tools && !policy.system && !policy.messages) return request
|
||||
|
||||
const hint = makeHint(policy.ttlSeconds)
|
||||
|
||||
@@ -10,19 +10,14 @@ const WebSearch = Schema.Struct({
|
||||
name: Schema.Literal("web_search"),
|
||||
user_location: MetaResponses.WebSearch.fields.user_location,
|
||||
})
|
||||
const MetaCacheControl = Schema.Struct({
|
||||
type: Schema.tag("ephemeral"),
|
||||
ttl: Schema.optional(Schema.Literals(["5m", "1h"])),
|
||||
})
|
||||
const FunctionTool = Schema.Struct({
|
||||
name: Schema.String,
|
||||
description: Schema.String,
|
||||
input_schema: JsonObject,
|
||||
cache_control: Schema.optional(MetaCacheControl),
|
||||
})
|
||||
const Body = Schema.Struct({
|
||||
...AnthropicMessages.AnthropicMessagesBody.fields,
|
||||
tools: optionalArray(Schema.Union([FunctionTool, WebSearch])),
|
||||
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) {
|
||||
|
||||
@@ -426,7 +426,6 @@ interface ActiveContent {
|
||||
readonly type: "text" | "reasoning"
|
||||
readonly id: string
|
||||
readonly thinking?: MistralThinkingContent
|
||||
readonly thinkingUnits?: MistralThinkingUnit[]
|
||||
}
|
||||
|
||||
export interface ParserState {
|
||||
@@ -503,8 +502,8 @@ const closeActive = (state: ParserState, events: LLMEvent[]) => {
|
||||
state.lifecycle,
|
||||
events,
|
||||
state.active.id,
|
||||
thinkingMetadata({ ...state.active.thinking, type: "thinking", thinking: state.active.thinkingUnits ?? [] }),
|
||||
thinkingText(state.active.thinkingUnits ?? []),
|
||||
thinkingMetadata(state.active.thinking ?? { type: "thinking", thinking: [] }),
|
||||
thinkingText(state.active.thinking?.thinking ?? []),
|
||||
)
|
||||
return { ...state, lifecycle, active: undefined }
|
||||
}
|
||||
@@ -525,23 +524,20 @@ const appendThinking = (state: ParserState, events: LLMEvent[], part: MistralOut
|
||||
const current = state.active?.type === "reasoning" ? state : closeActive(state, events)
|
||||
const units = thinkingUnits(part.thinking)
|
||||
const active = current.active ?? { type: "reasoning" as const, id: `reasoning-${current.nextContent}` }
|
||||
// Keep native units out of streamed events until the block is complete.
|
||||
const accumulated = active.thinkingUnits ?? []
|
||||
accumulated.push(...units)
|
||||
const thinking = {
|
||||
...active.thinking,
|
||||
...part,
|
||||
type: "thinking" as const,
|
||||
thinking: [],
|
||||
thinking: [...(active.thinking?.thinking ?? []), ...units],
|
||||
}
|
||||
const text = thinkingText(units)
|
||||
return {
|
||||
...current,
|
||||
lifecycle:
|
||||
text.length > 0
|
||||
? Lifecycle.reasoningDelta(current.lifecycle, events, active.id, text)
|
||||
: Lifecycle.reasoningStart(current.lifecycle, events, active.id),
|
||||
active: { ...active, thinking, thinkingUnits: accumulated },
|
||||
? Lifecycle.reasoningDelta(current.lifecycle, events, active.id, text, thinkingMetadata(thinking))
|
||||
: Lifecycle.reasoningStart(current.lifecycle, events, active.id, thinkingMetadata(thinking)),
|
||||
active: { ...active, thinking },
|
||||
nextContent: current.active ? current.nextContent : current.nextContent + 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
type LLMRequest,
|
||||
type MediaPart,
|
||||
type ReasoningPart,
|
||||
type TextPart,
|
||||
type ToolCallPart,
|
||||
type ToolDefinition,
|
||||
} from "../schema/index.js"
|
||||
@@ -44,7 +45,6 @@ const OpenAIChatCacheControl = Schema.Struct({
|
||||
type: Schema.Literal("ephemeral"),
|
||||
ttl: Schema.optional(Schema.String),
|
||||
})
|
||||
type OpenAIChatCacheControl = Schema.Schema.Type<typeof OpenAIChatCacheControl>
|
||||
|
||||
const OpenAIChatFunction = Schema.Struct({
|
||||
name: Schema.String,
|
||||
@@ -121,15 +121,12 @@ const decodeReasoningDetail = Schema.decodeUnknownOption(ReasoningDetail)
|
||||
const knownReasoningDetails = (details: ReadonlyArray<unknown>) =>
|
||||
details.flatMap((detail) => Option.toArray(decodeReasoningDetail(detail)))
|
||||
|
||||
const OpenAIChatTextContent = Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
text: Schema.String,
|
||||
cache_control: Schema.optional(OpenAIChatCacheControl),
|
||||
})
|
||||
type OpenAIChatTextContent = Schema.Schema.Type<typeof OpenAIChatTextContent>
|
||||
|
||||
const OpenAIChatUserContent = Schema.Union([
|
||||
OpenAIChatTextContent,
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("text"),
|
||||
text: Schema.String,
|
||||
cache_control: Schema.optional(OpenAIChatCacheControl),
|
||||
}),
|
||||
Schema.Struct({
|
||||
type: Schema.Literal("image_url"),
|
||||
image_url: Schema.Struct({ url: Schema.String }),
|
||||
@@ -139,7 +136,6 @@ const OpenAIChatUserContent = Schema.Union([
|
||||
file: Schema.Struct({ filename: Schema.String, file_data: Schema.String }),
|
||||
}),
|
||||
])
|
||||
type OpenAIChatUserContent = Schema.Schema.Type<typeof OpenAIChatUserContent>
|
||||
|
||||
const OpenAIChatMessage = Schema.Union([
|
||||
Schema.Struct({
|
||||
@@ -153,19 +149,21 @@ const OpenAIChatMessage = Schema.Union([
|
||||
Schema.StructWithRest(
|
||||
Schema.Struct({
|
||||
role: Schema.Literal("assistant"),
|
||||
content: Schema.NullOr(Schema.Union([Schema.String, Schema.Array(OpenAIChatTextContent)])),
|
||||
content: Schema.NullOr(Schema.String),
|
||||
tool_calls: optionalArray(OpenAIChatAssistantToolCall),
|
||||
reasoning_content: Schema.optional(Schema.String),
|
||||
reasoning: Schema.optional(Schema.String),
|
||||
reasoning_text: Schema.optional(Schema.String),
|
||||
reasoning_details: Schema.optional(Schema.Unknown),
|
||||
cache_control: Schema.optional(OpenAIChatCacheControl),
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Unknown)],
|
||||
),
|
||||
Schema.Struct({
|
||||
role: Schema.Literal("tool"),
|
||||
tool_call_id: Schema.String,
|
||||
content: Schema.Union([Schema.String, Schema.Array(OpenAIChatTextContent)]),
|
||||
content: Schema.String,
|
||||
cache_control: Schema.optional(OpenAIChatCacheControl),
|
||||
}),
|
||||
]).pipe(Schema.toTaggedUnion("role"))
|
||||
type OpenAIChatMessage = Schema.Schema.Type<typeof OpenAIChatMessage>
|
||||
@@ -329,7 +327,9 @@ export interface ParserState {
|
||||
// OpenAI Chat wire format. Keep provider quirks here instead of leaking native
|
||||
// fields into `LLMRequest`.
|
||||
interface LoweringOptions {
|
||||
readonly cacheControl?: (cache: CacheHint | undefined) => OpenAIChatCacheControl | undefined
|
||||
readonly cacheControl?: (
|
||||
cache: CacheHint | undefined,
|
||||
) => Schema.Schema.Type<typeof OpenAIChatCacheControl> | undefined
|
||||
readonly toolCallID?: (id: string) => string
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (
|
||||
message: OpenAIChatRequestMessage,
|
||||
options: LoweringOptions,
|
||||
) {
|
||||
const content: OpenAIChatUserContent[] = []
|
||||
const content: Array<Schema.Schema.Type<typeof OpenAIChatUserContent>> = []
|
||||
for (const part of message.content) {
|
||||
if (part.type === "text") {
|
||||
content.push({ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) })
|
||||
@@ -441,14 +441,14 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
|
||||
requireReasoning: boolean,
|
||||
options: LoweringOptions & { readonly providerMetadataKey: string },
|
||||
) {
|
||||
const content: OpenAIChatTextContent[] = []
|
||||
const content: TextPart[] = []
|
||||
const reasoning: ReasoningPart[] = []
|
||||
const toolCalls: OpenAIChatAssistantToolCall[] = []
|
||||
for (const part of message.content) {
|
||||
if (!ProviderShared.supportsContent(part, ["text", "reasoning", "tool-call"]))
|
||||
return yield* ProviderShared.unsupportedContent("OpenAI Chat", "assistant", ["text", "reasoning", "tool-call"])
|
||||
if (part.type === "text") {
|
||||
content.push({ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) })
|
||||
content.push(part)
|
||||
continue
|
||||
}
|
||||
if (part.type === "reasoning") {
|
||||
@@ -486,15 +486,14 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
|
||||
if (reasoning.length === 0) return nativeReasoning ?? (requireReasoning ? "" : undefined)
|
||||
return text
|
||||
})()
|
||||
const cached = message.content.findLast((part) => "cache" in part && part.cache !== undefined)
|
||||
const cacheControl = options.cacheControl?.(cached && "cache" in cached ? cached.cache : undefined)
|
||||
const result = {
|
||||
role: "assistant" as const,
|
||||
content: (() => {
|
||||
if (content.some((part) => part.cache_control !== undefined)) return content
|
||||
if (content.length === 0 && toolCalls.length > 0) return null
|
||||
return content.map((part) => part.text).join("")
|
||||
})(),
|
||||
content: content.length > 0 ? content.map((part) => part.text).join("") : toolCalls.length > 0 ? null : "",
|
||||
...(toolCalls.length > 0 ? { tool_calls: toolCalls } : {}),
|
||||
...(details !== undefined ? { reasoning_details: details } : {}),
|
||||
...(cacheControl !== undefined ? { cache_control: cacheControl } : {}),
|
||||
}
|
||||
if (field === undefined || reasoningText === undefined) return result
|
||||
return { ...result, [field]: reasoningText }
|
||||
@@ -505,38 +504,33 @@ const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (
|
||||
options: LoweringOptions,
|
||||
) {
|
||||
const messages: OpenAIChatMessage[] = []
|
||||
const attachments: OpenAIChatUserContent[] = []
|
||||
const attachments: Array<Schema.Schema.Type<typeof OpenAIChatUserContent>> = []
|
||||
for (const part of message.content) {
|
||||
if (!ProviderShared.supportsContent(part, ["tool-result"]))
|
||||
return yield* ProviderShared.unsupportedContent("OpenAI Chat", "tool", ["tool-result"])
|
||||
if (part.result.type !== "content") {
|
||||
messages.push(
|
||||
toolMessage(
|
||||
options.toolCallID?.(part.id) ?? part.id,
|
||||
ProviderShared.toolResultText(part),
|
||||
options.cacheControl?.(part.cache),
|
||||
),
|
||||
)
|
||||
messages.push({
|
||||
role: "tool",
|
||||
tool_call_id: options.toolCallID?.(part.id) ?? part.id,
|
||||
content: ProviderShared.toolResultText(part),
|
||||
cache_control: options.cacheControl?.(part.cache),
|
||||
})
|
||||
continue
|
||||
}
|
||||
const content: ReadonlyArray<Tool.Content> = part.result.value
|
||||
const text = content.filter((item) => item.type === "text").map((item) => item.text)
|
||||
messages.push(
|
||||
toolMessage(options.toolCallID?.(part.id) ?? part.id, text.join("\n"), options.cacheControl?.(part.cache)),
|
||||
)
|
||||
messages.push({
|
||||
role: "tool",
|
||||
tool_call_id: options.toolCallID?.(part.id) ?? part.id,
|
||||
content: text.join("\n"),
|
||||
cache_control: options.cacheControl?.(part.cache),
|
||||
})
|
||||
const files = content.filter((item) => item.type === "file")
|
||||
attachments.push(...(yield* Effect.forEach(files, (item) => lowerMedia(ProviderShared.toolFileMedia(item)))))
|
||||
}
|
||||
return { messages, attachments }
|
||||
})
|
||||
|
||||
// Chat cache breakpoints belong on text content parts, not on the message itself.
|
||||
const toolMessage = (toolCallID: string, text: string, cacheControl: OpenAIChatCacheControl | undefined) => ({
|
||||
role: "tool" as const,
|
||||
tool_call_id: toolCallID,
|
||||
content: cacheControl === undefined ? text : [{ type: "text" as const, text, cache_control: cacheControl }],
|
||||
})
|
||||
|
||||
const lowerMessage = Effect.fn("OpenAIChat.lowerMessage")(function* (
|
||||
message: OpenAIChatRequestMessage,
|
||||
reasoningField: string | undefined,
|
||||
@@ -595,7 +589,7 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request:
|
||||
if (requireAssistantAfterTool && messages.at(-1)?.role === "tool")
|
||||
messages.push({ role: "assistant", content: "Done." })
|
||||
}
|
||||
const pendingAttachments: OpenAIChatUserContent[] = []
|
||||
const pendingAttachments: Array<Schema.Schema.Type<typeof OpenAIChatUserContent>> = []
|
||||
const flushAttachments = () => {
|
||||
if (pendingAttachments.length === 0) return
|
||||
bridgeTools()
|
||||
@@ -605,25 +599,24 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request:
|
||||
if (message.role === "user") bridgeTools()
|
||||
if (message.role === "system") {
|
||||
const part = yield* ProviderShared.wrappedSystemUpdate("OpenAI Chat", message)
|
||||
const cacheControl = options.cacheControl?.(part.cache)
|
||||
if (pendingAttachments.length > 0) {
|
||||
messages.push({
|
||||
role: "user",
|
||||
content: [
|
||||
...pendingAttachments.splice(0),
|
||||
{ type: "text", text: part.text, cache_control: cacheControl },
|
||||
{ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) },
|
||||
],
|
||||
})
|
||||
continue
|
||||
}
|
||||
const previous = messages.at(-1)
|
||||
if (previous?.role === "user" && typeof previous.content === "string")
|
||||
messages[messages.length - 1] = cacheControl
|
||||
messages[messages.length - 1] = options.cacheControl?.(part.cache)
|
||||
? {
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: previous.content },
|
||||
{ type: "text", text: part.text, cache_control: cacheControl },
|
||||
{ type: "text", text: part.text, cache_control: options.cacheControl(part.cache) },
|
||||
],
|
||||
}
|
||||
: { role: "user", content: `${previous.content}\n${part.text}` }
|
||||
@@ -632,15 +625,15 @@ const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request:
|
||||
role: "user",
|
||||
content: [
|
||||
...previous.content,
|
||||
{ type: "text", text: part.text, cache_control: cacheControl },
|
||||
{ type: "text", text: part.text, cache_control: options.cacheControl?.(part.cache) },
|
||||
],
|
||||
}
|
||||
else
|
||||
messages.push(
|
||||
cacheControl
|
||||
options.cacheControl?.(part.cache)
|
||||
? {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: part.text, cache_control: cacheControl }],
|
||||
content: [{ type: "text", text: part.text, cache_control: options.cacheControl(part.cache) }],
|
||||
}
|
||||
: { role: "user", content: part.text },
|
||||
)
|
||||
|
||||
@@ -33,7 +33,8 @@ const patterns = [
|
||||
/context[_ ]length[_ ]exceeded/i,
|
||||
/context length is only \d+ tokens/i,
|
||||
/input length.*exceeds.*context length/i,
|
||||
/prompt too long; exceeded (?:max )?context length/i,
|
||||
// Z.ai code 1261 arrives as `Prompt too long` or `Prompt 超长`.
|
||||
/prompt (?:too long|超长)/i,
|
||||
/too large for model with \d+ maximum context length/i,
|
||||
/prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i,
|
||||
/model_context_window_exceeded/i,
|
||||
@@ -145,13 +146,15 @@ const CONTENT_POLICY_CODES = new Set([
|
||||
const GATEWAY_CODE_LABEL = /^[^:\n]+: \[([A-Za-z0-9_.-]+)\]/
|
||||
const RATE_LIMIT_TEXT = /rate increased too quickly|rate[-_\s]?limit|too[_\s]?many[_\s]?requests/i
|
||||
// Only consulted on 429, where throttles and account caps share a status.
|
||||
const QUOTA_TEXT = /insufficient[-_\s]?quota|quota[-_\s]?exceeded|budget exceeded|usage limit/i
|
||||
// Z.ai reports balance, plan expiry, plan limits, plan model access, and fair-use restrictions on 429.
|
||||
const QUOTA_TEXT =
|
||||
/insufficient[-_\s]?(?:quota|balance)|quota[-_\s]?exceeded|budget exceeded|usage limit|limit exhausted|package has expired|plan does not yet include|fair usage policy/i
|
||||
// Policy rejections without a dedicated code, matched against the provider's own
|
||||
// explanation only. OpenAI reuses `invalid_prompt` for usage-policy rejections while
|
||||
// Bedrock Mantle reuses it for schema validation; Anthropic reports blocked output
|
||||
// under `invalid_request_error`.
|
||||
const CONTENT_POLICY_TEXT =
|
||||
/violating our usage policy|blocked by content filtering policy|content[-_\s]?policy|rejected as a result of our safety system/i
|
||||
/violating our usage policy|blocked by content filtering policy|content[-_\s]?policy|rejected as a result of our safety system|detected potentially unsafe or sensitive content/i
|
||||
const SERVER_ERROR_TEXT =
|
||||
/\b(?:try again|(?:please |you can )?retry (?:the |this |your )?request|try (?:the |this |your )?request again|(?:currently |temporarily )?at capacity|overloaded|temporarily unavailable|service[-_\s]?unavailable|(?:server|internal)[-_\s]?error|server (?:is )?busy|provider returned (?:an )?error|resource[-_\s]?exhausted|upstream (?:connect|connection|request)|request buffer limit while retrying upstream)\b/i
|
||||
|
||||
|
||||
@@ -3,17 +3,7 @@ import { Effect } from "effect"
|
||||
import { CacheHint, LLM, Message } from "../src/index.js"
|
||||
import { Auth } from "../src/route.js"
|
||||
import { compileRequest } from "../src/route/client.js"
|
||||
import {
|
||||
Alibaba,
|
||||
AmazonBedrock,
|
||||
AnthropicCompatible,
|
||||
CloudflareAIGateway,
|
||||
GoogleVertexMessages,
|
||||
Meta,
|
||||
MiniMax,
|
||||
Moonshot,
|
||||
ZAICodingPlan,
|
||||
} from "../src/providers.js"
|
||||
import { AmazonBedrock, AnthropicCompatible, GoogleVertexMessages } from "../src/providers.js"
|
||||
import * as AnthropicMessages from "../src/protocols/anthropic-messages.js"
|
||||
import * as Gemini from "../src/protocols/gemini.js"
|
||||
import * as OpenAIChat from "../src/protocols/openai-chat.js"
|
||||
@@ -137,34 +127,6 @@ describe("applyCachePolicy", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
const messagesModels = [
|
||||
["alibaba-messages", Alibaba.configure({ region: "ap-southeast-1", apiKey: "test" }).messages("qwen3.8-max")],
|
||||
[
|
||||
"cloudflare-ai-gateway-messages",
|
||||
CloudflareAIGateway.configure({ accountId: "test", gatewayId: "test", apiKey: "test" }).model(
|
||||
"anthropic/claude-sonnet-4-6",
|
||||
),
|
||||
],
|
||||
["meta-messages", Meta.configure({ apiKey: "test" }).messages("muse-spark-1.3")],
|
||||
["minimax-messages", MiniMax.configure({ apiKey: "test" }).model("MiniMax-M3")],
|
||||
["moonshot-messages", Moonshot.configure({ apiKey: "test" }).messages("kimi-k3")],
|
||||
["zai-coding-messages", ZAICodingPlan.configure({ apiKey: "test" }).messages("glm-5.3")],
|
||||
] as const
|
||||
|
||||
messagesModels.forEach(([route, model]) =>
|
||||
it.effect(`'auto' emits cache markers on ${route}`, () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(LLM.request({ model, system: "Sys", prompt: "hi" }))
|
||||
|
||||
expect(prepared.route).toBe(route)
|
||||
expect(prepared.body).toMatchObject({
|
||||
system: [{ type: "text", text: "Sys", cache_control: { type: "ephemeral" } }],
|
||||
messages: [{ role: "user", content: [{ type: "text", text: "hi", cache_control: { type: "ephemeral" } }] }],
|
||||
})
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
it.effect("'auto' is a no-op on OpenAI (implicit caching protocol)", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
|
||||
Vendored
+3
-3
@@ -10,7 +10,7 @@
|
||||
"usage"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-7-plus-streams-thinking-disabled",
|
||||
"recordedAt": "2026-09-29T05:26:24.450Z"
|
||||
"recordedAt": "2026-09-08T03:10:57.819Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -21,14 +21,14 @@
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":4096,\"thinking\":{\"type\":\"disabled\"}}"
|
||||
"body": "{\"model\":\"qwen3.7-plus\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219? Reply with only the final integer.\"}]}],\"stream\":true,\"max_tokens\":4096,\"thinking\":{\"type\":\"disabled\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.7-plus\",\"id\":\"msg_39067759-5f01-9b3a-b2c1-09111797f7b5\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":20,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"3\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"788\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"7\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"output_tokens\":5,\"cache_creation_input_tokens\":0,\"input_tokens\":32,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.7-plus\",\"id\":\"msg_c4d58b4f-a61d-9d0c-a9e4-cb45d34b1120\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":20,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"3\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"7887\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":5,\"cache_creation_input_tokens\":0,\"input_tokens\":32,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+3
-3
@@ -9,7 +9,7 @@
|
||||
"structured-output"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-8-max-follows-a-json-schema",
|
||||
"recordedAt": "2026-09-29T05:26:41.466Z"
|
||||
"recordedAt": "2026-09-08T03:11:30.530Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -20,14 +20,14 @@
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Return a JSON object with one key \\\"city\\\" set to the capital city of France.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"disabled\"},\"output_config\":{\"format\":{\"type\":\"json_schema\",\"schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}}}"
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Return a JSON object with one key \\\"city\\\" set to the capital city of France.\"}]}],\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"disabled\"},\"output_config\":{\"format\":{\"type\":\"json_schema\",\"schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}},\"required\":[\"city\"],\"additionalProperties\":false}}}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_61ee9b9d-a224-9b69-98f7-9f9ea3937b41\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":18,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"{\\\"\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"city\\\":\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"Paris\\\"}\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"output_tokens\":6,\"cache_creation_input_tokens\":0,\"input_tokens\":32,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_16e067c1-984b-94d3-9abb-11792d794271\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":18,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"type\":\"text\",\"text\":\"\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"{\\\"\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\"city\\\":\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"Paris\\\"}\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":6,\"cache_creation_input_tokens\":0,\"input_tokens\":32,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+3
-3
@@ -10,7 +10,7 @@
|
||||
"tool-choice"
|
||||
],
|
||||
"name": "alibaba-messages/qwen-3-8-max-obeys-named-tool-choice",
|
||||
"recordedAt": "2026-09-29T05:26:40.212Z"
|
||||
"recordedAt": "2026-09-08T03:11:06.590Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -21,14 +21,14 @@
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Find the current weather in Paris.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"]},\"cache_control\":{\"type\":\"ephemeral\"}}],\"tool_choice\":{\"type\":\"tool\",\"name\":\"get_weather\"},\"stream\":true,\"max_tokens\":4096,\"thinking\":{\"type\":\"disabled\"}}"
|
||||
"body": "{\"model\":\"qwen3.8-max\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Find the current weather in Paris.\"}]}],\"tools\":[{\"name\":\"get_weather\",\"description\":\"Get weather in a city\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"]}}],\"tool_choice\":{\"type\":\"tool\",\"name\":\"get_weather\"},\"stream\":true,\"max_tokens\":4096,\"thinking\":{\"type\":\"disabled\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_dab3e1c5-e75e-978d-902e-1b0a82918cff\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":45,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"name\":\"get_weather\",\"input\":{},\"id\":\"toolu_9a1c3c06a04744cc97df33a2\",\"type\":\"tool_use\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"{\\\"city\\\": \\\"Paris\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"\\\"\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"}\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"cache_creation\":{\"ephemeral_5m_input_tokens\":0},\"output_tokens\":19,\"cache_creation_input_tokens\":0,\"input_tokens\":288,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
"body": "event:ping\ndata:{\"type\":\"ping\"}\n\nevent:message_start\ndata:{\"message\":{\"model\":\"qwen3.8-max\",\"id\":\"msg_0e8f1abf-f2bc-9a86-a6a7-14804ac17eac\",\"role\":\"assistant\",\"type\":\"message\",\"content\":[],\"usage\":{\"input_tokens\":45,\"output_tokens\":0}},\"type\":\"message_start\"}\n\nevent:content_block_start\ndata:{\"type\":\"content_block_start\",\"content_block\":{\"name\":\"get_weather\",\"input\":{},\"id\":\"toolu_cf9cab33261f4709ae096d8a\",\"type\":\"tool_use\"},\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"{\\\"city\\\": \\\"Paris\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"\\\"\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_delta\ndata:{\"delta\":{\"partial_json\":\"}\",\"type\":\"input_json_delta\"},\"type\":\"content_block_delta\",\"index\":0}\n\nevent:content_block_stop\ndata:{\"type\":\"content_block_stop\",\"index\":0}\n\nevent:message_delta\ndata:{\"delta\":{\"stop_reason\":\"end_turn\"},\"type\":\"message_delta\",\"usage\":{\"output_tokens\":19,\"cache_creation_input_tokens\":0,\"input_tokens\":288,\"cache_read_input_tokens\":0,\"prompt_tokens_details\":{\"cached_tokens\":0}}}\n\nevent:message_stop\ndata:{\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+7
-7
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
+6
-13
@@ -2,16 +2,9 @@
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"model": "muse-spark-1.3",
|
||||
"tags": [
|
||||
"prefix:meta-messages",
|
||||
"provider:meta",
|
||||
"protocol:meta-messages",
|
||||
"tool",
|
||||
"tool-loop",
|
||||
"reasoning"
|
||||
],
|
||||
"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-29T05:29:52.700Z"
|
||||
"recordedAt": "2026-09-07T17:27:03.540Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -22,14 +15,14 @@
|
||||
"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.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"name\":\"lookup_weather\",\"description\":\"Look up current weather\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false},\"cache_control\":{\"type\":\"ephemeral\"}}],\"tool_choice\":{\"type\":\"auto\"},\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"adaptive\",\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
|
||||
"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_6abb4ccec3772e65cd44417d\",\"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-PaDgFT_IXY3RXUUduHqe1_WSgTF5_gMh29lCBV6vhfXW34eU6nJO7gUfRPczMK4nZfQhrqQewRujwjtNKR69DHHqTP35Q0jDnuZ4GZX9qBZT1OH9JhcMl1FxvccpatEcuH2Nju9Rf12KFHAOm9eKK6wFiaZgEHcT1wPbRYVZVZ8VxLeKk-ED3wkmWFhUFJRLSgJguWBMvEMbtIUlAKsJ6qkUxClfQKMm3eft1yf9ajseT32eloGzBE48IhyXu6tpeUDs9UatFgZOHKtHmvtKsPqRw-ddbimLnMtdkexwa8xR7EVTVuwBezunqhssfYAdR4c4t6VDpACh2UL3LcVUF9b0reiwpUfdpYrwjpB3bTCWyzGjwvIwGeuoR6VrWHz5nqVbt3bBprJXqJ4IRLKAXFFlMhClkz0MFNPBufcXSdh9i9NjbNp5c8lXCSkhMfhYX9B9KbQKl3DxUCjEY_VZk0nQJwt9zQVwTzWPzl9fTkBWrdebpXCg-z4lPmU5GLyGY3MlF24eDoSfJfqibF1HHqztluSHi4XEulrwMh4dpZmLhlLxlFxwnC8LjY_Rg8uOyzeoCzsMj-R9WrZ6n9jLZrxJPVo17nFjsuSwt6zMRS9Cna6adGFso0jIKF4BNTcZXlVt0b_TWA9AY4xpGSaAQXKYRBdAZNzTK5hj-bvyfWVTBepTBKm7pAtJW2e05mVBFDqRwEr3kcKkMTRMEj8vztSX4sNjV7Sw6bDbPjb_uFeoaNkq1ua9N7ZkKsyLn80ysWQ6IL-B_AVM1toAI0bVVYQmHbRSz1MgybcT5vjK2TWfCgxBuqw1ARU3oEM1iWTvpuHkFDNGeZnT4IWc_z_vLtQVnqSlqXPtnAmJ0WJgvQGSBhojSa9QMxRz_l4LZHmXUtfdMdUHTbrZwbfoL6eIyZf43ah-sXILJWTX9wXuYqnaclwvuO3EFiDUioSIBxWvAj2HNe5HpzayuvJqneRAukiVynt9SOo79VcBKAFsHW_KhUz58KjndfvOFP7byXv_W6Xa00ve_3GEaCANjFIl7ratBEuFYtpbVTT7TGIyGmuPQTVnGe7CAcFiAvGSwoaMwqvdGheKxeR7_cZvSYREeUnL5mXev8-MKY6_AtddOanQhf9pshxAXrIUtVFqin5MGhL2j787niURU9-o_VhLgzaIqfjbfMKkzdZ3-ESyDloOeD3CwoAsD_AVIrQZQHYbpYzFYasH0VrItar07ItxWZt_sCehTOi7Jtk5G8e7fd1HtCWmrp2iArioSIp8aLOphI9lazM7taIpfF_y0EMt3x5ENSvhtEmSk4kCJQnNyRv21uHT4uD0XjdQJbHeoRhzHDxoLHUOqAX9fGBzMFO8ZsWJxl8ebiCJeJ9Jzzr9bXeUKH5cP2VymoXPs3AWBrTpw1WfADM5sjHDJLzw\",\"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_01a0eba40810714cbd0a8a84496badc7\",\"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\":0,\"input_tokens\":564,\"output_tokens\":97,\"output_tokens_details\":{\"thinking_tokens\":29}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -40,14 +33,14 @@
|
||||
"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-PaDgFT_IXY3RXUUduHqe1_WSgTF5_gMh29lCBV6vhfXW34eU6nJO7gUfRPczMK4nZfQhrqQewRujwjtNKR69DHHqTP35Q0jDnuZ4GZX9qBZT1OH9JhcMl1FxvccpatEcuH2Nju9Rf12KFHAOm9eKK6wFiaZgEHcT1wPbRYVZVZ8VxLeKk-ED3wkmWFhUFJRLSgJguWBMvEMbtIUlAKsJ6qkUxClfQKMm3eft1yf9ajseT32eloGzBE48IhyXu6tpeUDs9UatFgZOHKtHmvtKsPqRw-ddbimLnMtdkexwa8xR7EVTVuwBezunqhssfYAdR4c4t6VDpACh2UL3LcVUF9b0reiwpUfdpYrwjpB3bTCWyzGjwvIwGeuoR6VrWHz5nqVbt3bBprJXqJ4IRLKAXFFlMhClkz0MFNPBufcXSdh9i9NjbNp5c8lXCSkhMfhYX9B9KbQKl3DxUCjEY_VZk0nQJwt9zQVwTzWPzl9fTkBWrdebpXCg-z4lPmU5GLyGY3MlF24eDoSfJfqibF1HHqztluSHi4XEulrwMh4dpZmLhlLxlFxwnC8LjY_Rg8uOyzeoCzsMj-R9WrZ6n9jLZrxJPVo17nFjsuSwt6zMRS9Cna6adGFso0jIKF4BNTcZXlVt0b_TWA9AY4xpGSaAQXKYRBdAZNzTK5hj-bvyfWVTBepTBKm7pAtJW2e05mVBFDqRwEr3kcKkMTRMEj8vztSX4sNjV7Sw6bDbPjb_uFeoaNkq1ua9N7ZkKsyLn80ysWQ6IL-B_AVM1toAI0bVVYQmHbRSz1MgybcT5vjK2TWfCgxBuqw1ARU3oEM1iWTvpuHkFDNGeZnT4IWc_z_vLtQVnqSlqXPtnAmJ0WJgvQGSBhojSa9QMxRz_l4LZHmXUtfdMdUHTbrZwbfoL6eIyZf43ah-sXILJWTX9wXuYqnaclwvuO3EFiDUioSIBxWvAj2HNe5HpzayuvJqneRAukiVynt9SOo79VcBKAFsHW_KhUz58KjndfvOFP7byXv_W6Xa00ve_3GEaCANjFIl7ratBEuFYtpbVTT7TGIyGmuPQTVnGe7CAcFiAvGSwoaMwqvdGheKxeR7_cZvSYREeUnL5mXev8-MKY6_AtddOanQhf9pshxAXrIUtVFqin5MGhL2j787niURU9-o_VhLgzaIqfjbfMKkzdZ3-ESyDloOeD3CwoAsD_AVIrQZQHYbpYzFYasH0VrItar07ItxWZt_sCehTOi7Jtk5G8e7fd1HtCWmrp2iArioSIp8aLOphI9lazM7taIpfF_y0EMt3x5ENSvhtEmSk4kCJQnNyRv21uHT4uD0XjdQJbHeoRhzHDxoLHUOqAX9fGBzMFO8ZsWJxl8ebiCJeJ9Jzzr9bXeUKH5cP2VymoXPs3AWBrTpw1WfADM5sjHDJLzw\"},{\"type\":\"text\",\"text\":\"I'll look up the current weather in Paris.\"},{\"type\":\"tool_use\",\"id\":\"call_01a0eba40810714cbd0a8a84496badc7\",\"name\":\"lookup_weather\",\"input\":{\"city\":\"Paris\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"call_01a0eba40810714cbd0a8a84496badc7\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\"}\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"tools\":[{\"name\":\"lookup_weather\",\"description\":\"Look up current weather\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\",\"enum\":[\"Paris\"]}},\"required\":[\"city\"],\"additionalProperties\":false},\"cache_control\":{\"type\":\"ephemeral\"}}],\"tool_choice\":{\"type\":\"none\"},\"stream\":true,\"max_tokens\":1024,\"thinking\":{\"type\":\"adaptive\",\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
|
||||
"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_6abb4cd0b3f15768e1ae4287\",\"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-PaDgF70ZwJMdvZQh6urOFUjgHa2IrdJbBv8RFXDpivwOudmj9QoCi81uRWeArjn_7V2nNk6i6jlHMjN7ACeZUE-faBnl4bTI10Fs0w0SSgcdkMaume2hpXBrZB85wlEttQviz-Pc5EHhAzU5QFIrK83CQkhJENqcfY3NJW1-55JFqlXmWSFV0GtNp3clG7V0LR1Zaq54hqIUY5iwf1pB8VIXLxPca3LwXMGbQPgjOgj_WNKHEBGl6mIafeca1OlGuDrJPN9tWBhlfxJCkcRU0zl14NA8chEyr96N40KrPewjVAlWO3jSejy9V7PnglilNewhUXOEQ75w2tYiZDajBfz6iG_n27zD3uvgD3QQthdyeUhJqlBaX81zMIe39Ctfo-cX-62cKwE3_EHy_7SxkwY5Olbf_bwL4GGzR39MObZoyv8-ZU1kwwVuSLXuDFvIx6iaAMZ50U4xXYbOphAaw2rylql0_kiPlFS9V3Fr_Jvx4vplj-8xh9JI7Uu9t9O7ltECpN9Qfu32fqxzWy9-ZVIh15gzhyegNeICBOcz6b9s3CIFHnzRcU69Ru9-lbydKv7scII_2xxLd-wlHQDCXHXyTeTeZjbTRiul0u5J2m8gGefF-j0lVChJOh8F4vBltzYTFTiQoSP8KtL61VJOt6dLsxOQA0Rq4PR5Lcs2fAfJbKb7jYB23bmXvQQ1d76keuvq_yoSIhrSyxLvXRCxouoWttkI8QC0u7O5pZUIPVfiYMI-j3bbc63BFOu4wAGbckm8Txo-gAY8UYAAwMBX17SD5EcTR82njKmd1R0Y8XOJLiwZRlN0ou-syq8xhahVgd0VcyB1HKr1f9mbhJdqFkKGpWh9-SR6jT6fKCCFxeMA3caczpG5wP_q5SGgyUiK0GWYpHabjSeJWac-04TDOfqL4QwLEKuL4PjlINe9OZY1n1lPJa2_MS9YGt6qlgLe3uZSEBwHk-ul5_yRetU8bd7lYcwDtUHT6puEkdjqeYwYQ8XWkUjoYwvSIMB_2v_owa5p9gPzf5Fr_zWqG7pizrStNNHq1MjXQX8Pl4Z6e2LZ2NjItfjR1GKdErNEJWXyh0oHCQKD1fIOXl64lpQorAHUcKV2HZm_SkwV7kxvzAfhdeT9Fe3AXZmtPBS1m0ZMMYNMYsNKb813JnWu8LvNg_zgXLBWsiB-hl4kCk1NKVAsCoA0i8JUFnGGm_uQp6t06a5NQYEXvi5A0akZTacle4UTBflNdsIVR6tSaO_m49nnr-XYSIh3tEzfjOsXpFz5L_A9SBDdPSGpcMJrs00nl1wDpF78OKC8NhwkIx8ydwF5A-ecXf91SbgqzVgv2T-wzuziA1wjJbPsGzTxP_wULUc2P5-ftFAMpsiRHB_-7hq6ZGo3HwNWjK2DTTN_89u7RCjuErEVPXUv-2fQ\",\"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 right now.\",\"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\":152,\"output_tokens\":55,\"output_tokens_details\":{\"thinking_tokens\":38}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+4
-11
@@ -2,16 +2,9 @@
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"model": "muse-spark-1.3",
|
||||
"tags": [
|
||||
"prefix:meta-messages",
|
||||
"provider:meta",
|
||||
"protocol:meta-messages",
|
||||
"text",
|
||||
"reasoning",
|
||||
"adaptive"
|
||||
],
|
||||
"tags": ["prefix:meta-messages", "provider:meta", "protocol:meta-messages", "text", "reasoning", "adaptive"],
|
||||
"name": "meta-messages/streams-text-with-adaptive-thinking",
|
||||
"recordedAt": "2026-09-29T05:29:46.263Z"
|
||||
"recordedAt": "2026-09-07T17:26:18.565Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -22,14 +15,14 @@
|
||||
"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.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"adaptive\",\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
|
||||
"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_6abb4cc8199fc8fa6763470e\",\"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-PaDgEdq_xGJ7Hl5i3AUq-IGolHfh7VsPu8914OD3BSVMiPCf1QVZ4O553OKpiejFGIkjBEfR2gGxcXlTsKuK78wfAiGAft2yQIHPeCJt4Csoxc3UUX405gUHqO9M06WeU118pTrlITW4g4dElALKmE2zi_pkQzEvU07jPqsF4n-3-9FkEnJ3D5Ag0UsU4LI1Mn5-EwPyPkitspoB-ytXsugMGQTMMfInzNq0OKDDMAO88aRQv24EslYBviairfM5c-k6dqOdGfsg_SntRiZJVNoo1yowGhQuiMKXreeQiQj_hrSVEXo8PVybmaCXtfW0I_2rI7JKR1uO-fxUHZckcCvLKMS3Wa2cwPUM9MPAnq05l3lbD1LpqZ_UPlf534p8_0BmF4hVGI2pamDWkL--7UaWrGFYtwBc3r9GVW0aIZC1ArnGjXsInKCnJuJ-elKu8H0Wp8-lxrsEfqtmxCamrsil8jOkIMezwSMUGtawhcOSdOnyQRje2b3OXiRx7Y-XZjrBlErbS13tdMJhXGlVqp9tazXlKdIt9CpMfMxjYW-Z5VkrJNymsBcjW1r2x5DC2VzGcqcha91rBkskrhKGWix95cdaEqUkP66DW9jThk4RS__E_oyt8DMTpvCxoZ5pe_mLkuS-ZpYKFeZbRegegIQErP_yuhvPWCwTYZymW4uUTVEmxdJ1otAST4ICF4ChsbHLLzJc4XHO7TA5GcwEc69m-poLTFrJvuYRErYhHHONOGWZGoyaPXApsmytUW6-lH2OiVpViVrLg_qvDw1NwyTgguQfP1d6JJYTmnjgN-Pa9170lqGzMI1fVjsC9zY5e6qe6NiyOwpxbFoV-jfdzyfeKX5LMVacpj2xV6dk6dYW7zcIZ3rJJvCpnwJROCjKbSRa4RvBFWNW2xjiWi1tYltzsz1YR1RHZJEN8szlfJjdMrfJdR5GbOiiDMcwGgGNOF5kbQqLgkggzR-B4lUNGLvSyBjAd_AdLpab0r9j1XdKBPWLFiXfAtX8lPSfeCSV41LFxKopB-MIVOpgtrwW6dsEayU7_w4Tc1nBTtYOGf2cGUV2XYzEpt5vxzCDC-og8WZ2lr3hOczp543-_I8n2ZOQEEk1k3suykPiQen_wgcpRccm18Aqsjy2t3uitZ-p1p4g0s6tG6Y_DCS4ku713v9Uw2ezHosl4Qk5JVk-KNvi9ZCYb9Jmc7PqHubP6Ua_UTyIeTiBB60zK5EgatIglUv3_NudfsplEH8YJkMvoAPJk-HW7c2WwzoymfA4TFoCg8vt3YHGAtuiyqF17-S3sfhl9J6oX-7FFpGn6bRqgwguC6sHUQw3bqWtyG4LjUy3_qzWfCI5zSm1hxjd6YRaxkdlv37Ol3Ua-65b3gRmQoUlA7omPbHuC8r2TNnG_ltsp9EMJlz7HN-Gyggw\",\"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\":161,\"output_tokens_details\":{\"thinking_tokens\":149}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+4
-11
@@ -2,16 +2,9 @@
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"model": "muse-spark-1.3",
|
||||
"tags": [
|
||||
"prefix:meta-messages",
|
||||
"provider:meta",
|
||||
"protocol:meta-messages",
|
||||
"text",
|
||||
"reasoning",
|
||||
"enabled"
|
||||
],
|
||||
"tags": ["prefix:meta-messages", "provider:meta", "protocol:meta-messages", "text", "reasoning", "enabled"],
|
||||
"name": "meta-messages/streams-text-with-enabled-thinking",
|
||||
"recordedAt": "2026-09-29T05:29:50.023Z"
|
||||
"recordedAt": "2026-09-07T17:26:20.464Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -22,14 +15,14 @@
|
||||
"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.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":2048,\"thinking\":{\"type\":\"enabled\",\"budget_tokens\":1024,\"display\":\"omitted\"},\"output_config\":{\"effort\":\"low\"}}"
|
||||
"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_6abb4ccc0beea22bb95a41de\",\"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-PaDgGoJiKtl-javJ6dY04Ws79OK_mVBNQgu19iuCSru9wUy0jS0-87v1Lr1jC3I6cI8gnBvTl3HcRgR8MqmTOddFg5f_QSqhUweOVsCzaNL18Bjkye_cK3jGmlc9IsGLJ8dGFvFMPwflkMcofmwZ1P0EsMivXgwsDsWuiHP3PAP6_DfKgpVnNoz1oxNXgoVMTaNylNHtKHXpeC4_doHgWONMjhca771bIh_x0sRT-mQlONkr6HHgzzpCftEGOgnELEC7DlmJ46CDe_VSuaiUk8VLeNV4xyNm4XBHoyFuEn2N4tmG6mxo3cQlN2er8lY39XmY5gqy4ZIJddoY4sVFxBfL-axfDDv89hAhTYgXxH4dj4MyAj5TPWdyVZwunw2dRwf9JobQW-HCGcPxW6rqE7znOAegWSLD-mKRGZXNp7-kTY5Wicv9toV-VsBIFc1YpHFRpw36pxZftd2JnbW38dNYH0fO_QYnCknHxE9RA68uFpu_JvvpHOO0sFAuqwOCMynQyqFZRY9vVJBMYnuB_RIY_WCEuZwt7hV9es1aPk2QJyAWFh-LnNDzp9ktLaNwnnJxRpEK1r0PMbUsIQJMuxcNH7lCstE2dGBf02O4lH4HlZSHNsLcZuUjb01HCISf7cQXzIjG6rO_47m52h6bMpYZITElM66XVlKRE5V3wF_7sQ2BaT4g8GLxxTkgh-C0x8ISPVaU773cDvJSuOI-5FlUbviG9oQG_0PxU5ulQDCMSEM4wZENu6ZRlngTloFSxHJfeQvShiy9pC9kv-DuOT1stUT4HqpV0bV7WGrJjlX9l17JbMgjMBwzhNWTJwGiuw2Ya2cCveYgSzxMZtcI_YH9mv0n53c2h7_1Lq2-kVDx_R5UngrMLu9YxjIwIgK_UtbtnTXJ-q5X4DexgovoBPjzc2GOQeK0DoOnfOczVLMALAkQMnyxCQ65ZuawVuJFmIKY9ooznuqWjl5mLxVCn5Fyhk2uTKjFlbW1JO-RdvNF9CMRD2y2bY2Igy4w9Gh1R_xAhM7rwF0dt_p2g8CUjqsEyIn9bSGuRuzXfkxNz7KYfyCD2rkZc1gw8oU7jiLEpNl5gCkcj7bNql7lcDYgJtGSDRwZlIzZcb7MaxFHyFw7dXGtrOIvLL5eF8TOidjaRaY27xkCCBkQXWeIiX0qV5AuojjrpRAJsVVvdT5s2dHd1IYHU0nnsePU1Dd7gyB59upU9W1AaFrXjx0pn4MsNuYDcyLyzihq1lCeK23OSoXkwH9UI-zaLkV6MGUf_K0jMJPr6FrMgYF1x_SpXYOKSXPHhY5UE3pGLIL5_vFCRkAkMcvpw-H3tyljgnCZwt3g-Yuy4Uj05MS3PseQxuUor-0trgRBaBW4AdB925t2D41RJ4xp3HnEpjEkNFJ8Hsqbs3QxyVoTAvElXOrQ\",\"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\":198,\"output_tokens_details\":{\"thinking_tokens\":186}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+5
-5
File diff suppressed because one or more lines are too long
+3
-3
@@ -11,7 +11,7 @@
|
||||
"reasoning"
|
||||
],
|
||||
"name": "minimax-messages/m2-7-streams-default-thinking",
|
||||
"recordedAt": "2026-09-29T05:27:50.902Z"
|
||||
"recordedAt": "2026-09-07T16:53:20.721Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -22,14 +22,14 @@
|
||||
"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.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":1536}"
|
||||
"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\":\"419c5ecf093decc95250bc4cc3afce52\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"MiniMax-M2.7\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":0,\"output_tokens\":0,\"cache_creation_input_tokens\":21,\"cache_read_input_tokens\":36}}}\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.\\\"\\n\\nWe need to calculate 173 * 219 = ?\\n\\nLet's compute.\\n\\n173 *\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" 219:\\n\\nOption 1: compute 173 * 200 = 34,600 (actually 173*200 = 34,600\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"? Wait 173 * 200 = 34,600? Let's check: 173 * 2 = 346, then times 100 = 34,600. Yes\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\".)\\n\\n173 * 19 = 173 * 20 - 173 = 3,460 - 173 = 3\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\",287.\\n\\nAdd them: 34,600 + 3,287 = 37,887.\\n\\nThus product is 37,887.\\n\\nAlternatively, compute \"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"219 * 173: 219*100 = 21,900; 219*70 = 15,330; 219*3 = 657\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"; sum = 21,900 + 15,330 = 37,230; +657 = 37,887. Same.\\n\\nThus answer: 378\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"87.\\n\\nThe user wants only the final integer. So reply \\\"37887\\\". No other text.\\n\\nThus final answer: 37887.\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"48b0f683a3fb6cfc2e060ff709c12a905c76d2e5bdddc709fb5b1cfc5aa600f7\"}}\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\":0,\"output_tokens\":259,\"cache_creation_input_tokens\":21,\"cache_read_input_tokens\":36,\"output_tokens_details\":{\"thinking_tokens\":256}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+5
-5
@@ -13,7 +13,7 @@
|
||||
"usage"
|
||||
],
|
||||
"name": "minimax-messages/m3-continues-a-tool-loop-with-adaptive-thinking",
|
||||
"recordedAt": "2026-09-29T05:27:53.421Z"
|
||||
"recordedAt": "2026-09-07T16:53:25.185Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -24,14 +24,14 @@
|
||||
"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.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"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},\"cache_control\":{\"type\":\"ephemeral\"}}],\"tool_choice\":{\"type\":\"auto\"},\"stream\":true,\"max_tokens\":1536,\"thinking\":{\"type\":\"adaptive\"}}"
|
||||
"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\":\"6e8c4645c02dd05c2f08c22e5eba9a89\",\"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 the weather in\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" one short sentence after\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" receiving the result.\"}}\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\":\"61f13458806572210eb8e1809b12f191297eea1a7f0104aa12f86595ef5eb1dc\"}}\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_01a0eba239b7718ba67e5aa3\",\"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\":68,\"cache_read_input_tokens\":203,\"service_tier\":\"standard\",\"output_tokens_details\":{\"thinking_tokens\":38}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -42,14 +42,14 @@
|
||||
"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 the weather in one short sentence after receiving the result. Let me call the tool.\",\"signature\":\"61f13458806572210eb8e1809b12f191297eea1a7f0104aa12f86595ef5eb1dc\"},{\"type\":\"tool_use\",\"id\":\"call_01a0eba239b7718ba67e5aa3\",\"name\":\"get_weather\",\"input\":{\"city\":\"Paris\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"tool_result\",\"tool_use_id\":\"call_01a0eba239b7718ba67e5aa3\",\"content\":\"{\\\"condition\\\":\\\"sunny\\\",\\\"temperature\\\":\\\"18C\\\"}\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"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},\"cache_control\":{\"type\":\"ephemeral\"}}],\"tool_choice\":{\"type\":\"none\"},\"stream\":true,\"max_tokens\":1536,\"thinking\":{\"type\":\"adaptive\"}}"
|
||||
"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\":\"0f6a842fc54eecab0d6df9ffebd8dd3b\",\"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 weather\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" in Paris is sunny\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" with a\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" temperature of 18\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"°C. I'll\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" report this in one\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"short sentence.\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"fefe2c0e5f3f0a0edc24b21d37c8b7fb12ed0755bd1898f71acc2325f1b35958\"}}\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\":\"It's\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" currently\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" sunny in\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" Paris with a temperature\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" of 18°C\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\".\"}}\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\":169,\"output_tokens\":38,\"cache_read_input_tokens\":128,\"service_tier\":\"standard\",\"output_tokens_details\":{\"thinking_tokens\":22}}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
@@ -11,7 +11,7 @@
|
||||
"usage"
|
||||
],
|
||||
"name": "minimax-messages/m3-generates-a-named-tool-call-with-default-thinking-off",
|
||||
"recordedAt": "2026-09-29T05:27:51.660Z"
|
||||
"recordedAt": "2026-09-07T16:53:24.366Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -22,14 +22,14 @@
|
||||
"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.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"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},\"cache_control\":{\"type\":\"ephemeral\"}}],\"tool_choice\":{\"type\":\"tool\",\"name\":\"get_weather\"},\"stream\":true,\"max_tokens\":512}"
|
||||
"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\":\"5f4a7ce22c5a82497fedc65028aa4825\",\"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_01a0eba235fb7281a0f2ea2c\",\"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\":287,\"output_tokens\":39,\"cache_read_input_tokens\":128,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+3
-3
@@ -11,7 +11,7 @@
|
||||
"thinking-off"
|
||||
],
|
||||
"name": "minimax-messages/m3-streams-text-with-thinking-disabled",
|
||||
"recordedAt": "2026-09-29T05:27:36.888Z"
|
||||
"recordedAt": "2026-09-07T16:53:16.028Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -22,14 +22,14 @@
|
||||
"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.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"max_tokens\":1536,\"thinking\":{\"type\":\"disabled\"}}"
|
||||
"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\":\"444066751666f2071c999bfd0b4c16b0\",\"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\":179,\"output_tokens\":3,\"service_tier\":\"standard\"}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+4
-11
File diff suppressed because one or more lines are too long
+4
-11
File diff suppressed because one or more lines are too long
Vendored
+4
-11
@@ -2,16 +2,9 @@
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"model": "kimi-k3",
|
||||
"tags": [
|
||||
"prefix:moonshot-messages",
|
||||
"provider:moonshot",
|
||||
"protocol:messages",
|
||||
"tool",
|
||||
"tool-choice",
|
||||
"usage"
|
||||
],
|
||||
"tags": ["prefix:moonshot-messages", "provider:moonshot", "protocol:messages", "tool", "tool-choice", "usage"],
|
||||
"name": "moonshot-messages/k3-respects-tool-choice-required",
|
||||
"recordedAt": "2026-09-29T05:28:21.798Z"
|
||||
"recordedAt": "2026-09-07T20:37:07.770Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -22,14 +15,14 @@
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"kimi-k3\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"Use get_weather to look up the current weather in Paris.\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"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},\"cache_control\":{\"type\":\"ephemeral\"}}],\"tool_choice\":{\"type\":\"any\"},\"stream\":true,\"max_tokens\":4096,\"output_config\":{\"effort\":\"low\"}}"
|
||||
"body": "{\"model\":\"kimi-k3\",\"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\":\"any\"},\"stream\":true,\"max_tokens\":4096,\"output_config\":{\"effort\":\"low\"}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"chatcmpl-c877bb3f-cb6b-9d0f-b91e-37849d7bff39\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"kimi-k3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":223,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"service_tier\":\"standard\",\"inference_geo\":\"not_available\",\"prompt_tokens\":223,\"cached_tokens\":0}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"The\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" user explicitly\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" asks to use\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" get_weather for\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" Paris. Need call\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" the\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" tool. Do\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" that.\"}}\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\":\"get_weather_0_7e180ea4\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\"}}\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_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"}\"}}\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\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":223,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":73,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens_details\":{\"thinking_tokens\":20},\"prompt_tokens\":223,\"completion_tokens\":73,\"total_tokens\":296,\"cached_tokens\":0}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
"body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"chatcmpl-2f2e02780e8a031b1b1307171\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"model\":\"kimi-k3\",\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":223,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":0,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\",\"prompt_tokens\":223,\"cached_tokens\":0}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"Need\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" use\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" get\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"_we\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"ather\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" for\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" Paris\"}}\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\":\"thinking_delta\",\"thinking\":\" Call\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" function\"}}\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\":\"thinking_delta\",\"thinking\":\" Then\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" final\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" summarize\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" result\"}}\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\":\"thinking_delta\",\"thinking\":\" Use\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" tool\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\".\"}}\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\":\"get_weather_0\",\"name\":\"get_weather\",\"input\":{}}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"city\\\": \\\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"Paris\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"}\"}}\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\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":0,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":223,\"output_tokens\":69,\"output_tokens_details\":{\"thinking_tokens\":19},\"prompt_tokens\":223,\"completion_tokens\":69,\"total_tokens\":292,\"cached_tokens\":223}}\n\nevent: message_stop\ndata: {\"type\":\"message_stop\"}\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+4
-10
File diff suppressed because one or more lines are too long
Vendored
+4
-11
File diff suppressed because one or more lines are too long
Vendored
+4
-11
File diff suppressed because one or more lines are too long
Vendored
+4
-11
File diff suppressed because one or more lines are too long
Vendored
+4
-11
File diff suppressed because one or more lines are too long
+7
-7
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@
|
||||
"model": "anthropic/claude-sonnet-4.6",
|
||||
"tags": ["prefix:openai-compatible-chat", "provider:openrouter", "protocol:openai-chat", "reasoning"],
|
||||
"name": "openrouter-reasoning",
|
||||
"recordedAt": "2026-09-29T16:19:02.835Z"
|
||||
"recordedAt": "2026-07-18T11:28:39.267Z"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
@@ -15,14 +15,14 @@
|
||||
"headers": {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
"body": "{\"model\":\"anthropic/claude-sonnet-4.6\",\"messages\":[{\"role\":\"system\",\"content\":[{\"type\":\"text\",\"text\":\"Think through the arithmetic, then reply with only the final integer.\",\"cache_control\":{\"type\":\"ephemeral\"}}]},{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"What is 173 multiplied by 219?\",\"cache_control\":{\"type\":\"ephemeral\"}}]}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"store\":false,\"max_completion_tokens\":1536,\"temperature\":0,\"usage\":{\"include\":true},\"reasoning\":{\"max_tokens\":1024}}"
|
||||
"body": "{\"model\":\"anthropic/claude-sonnet-4.6\",\"messages\":[{\"role\":\"system\",\"content\":\"Think through the arithmetic, then reply with only the final integer.\"},{\"role\":\"user\",\"content\":\"What is 173 multiplied by 219?\"}],\"stream\":true,\"stream_options\":{\"include_usage\":true},\"temperature\":0,\"reasoning\":{\"max_tokens\":1024},\"max_completion_tokens\":1536,\"store\":false,\"usage\":{\"include\":true}}"
|
||||
},
|
||||
"response": {
|
||||
"status": 200,
|
||||
"headers": {
|
||||
"content-type": "text/event-stream"
|
||||
},
|
||||
"body": "data: {\"id\":\"gen-1790698739-Acm5E9n2uVTO14sqrick\",\"object\":\"chat.completion.chunk\",\"created\":1790698739,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Claude Platform on AWS\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"173\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"173\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\n: OPENROUTER PROCESSING\n\ndata: {\"id\":\"gen-1790698739-Acm5E9n2uVTO14sqrick\",\"object\":\"chat.completion.chunk\",\"created\":1790698739,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Claude Platform on AWS\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" × 219\\n\\n173 × 200 = 34,600\\n173 × 19 = 173 × 20 - 173 = 3,460 - 173 = 3,287\\n\\n34,600 + 3,287 = 37,887\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" × 219\\n\\n173 × 200 = 34,600\\n173 × 19 = 173 × 20 - 173 = 3,460 - 173 = 3,287\\n\\n34,600 + 3,287 = 37,887\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1790698739-Acm5E9n2uVTO14sqrick\",\"object\":\"chat.completion.chunk\",\"created\":1790698739,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Claude Platform on AWS\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"signature\":\"Ev8CCrIBCBIYAipA0W4viH3kgBs43Cl5ewwVBPXTQElvzfbA2TLF4iSbKy9ZZDCSDjjAlF3Bs4ELEnP3vrrTuTioC6OB380lXQdyIDIRY2xhdWRlLXNvbm5ldC00LTY4AEIIdGhpbmtpbmdaJDRjMGYwNDZmLTI1ZmQtNDVmYi1iZmIzLWEwOGE4ZTI0OWNhN3oedXByb2ZfMDExQ2VkSE1TN0tEcTM4Z2JkUkp0bXN3qAH2ye/VBhIMhjcPOnE2wLhs0P05GgwA3z9luwbhnSyRMSQiMDYeIIThirdkJb/n2s2TxUk5EOMrSc1xejAb0RN7BIl66P5KhtwXvcZTiyCkBS8+7Cp6MOinWi+I8U7DhWskSA0pHZqaAncbV7qCIHkoZ5aT8jiPWplAOnlciDo2tLkGsLnzXrnfhI11GTF0kcfyhbrt3tjEDSiPstBbT2u19LJ5IqwV2VKl0pWVdl4PTsDwC09ClbnJj1y0A3AM9gEh4B9mEo4BvT0XVEncux4YAQ==.eyJlbmRwb2ludF9zbHVnIjoiYW50aHJvcGljL2NsYXVkZS00LjYtc29ubmV0LTIwMjYwMjE3fGNsYXVkZS1vbi1hd3MifQ\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1790698739-Acm5E9n2uVTO14sqrick\",\"object\":\"chat.completion.chunk\",\"created\":1790698739,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Claude Platform on AWS\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"37\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1790698739-Acm5E9n2uVTO14sqrick\",\"object\":\"chat.completion.chunk\",\"created\":1790698739,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Claude Platform on AWS\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"887\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1790698739-Acm5E9n2uVTO14sqrick\",\"object\":\"chat.completion.chunk\",\"created\":1790698739,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Claude Platform on AWS\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"stop\",\"native_finish_reason\":\"end_turn\"}]}\n\ndata: {\"id\":\"gen-1790698739-Acm5E9n2uVTO14sqrick\",\"object\":\"chat.completion.chunk\",\"created\":1790698739,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Claude Platform on AWS\",\"service_tier\":\"default\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"end_turn\"}],\"usage\":{\"prompt_tokens\":61,\"completion_tokens\":80,\"total_tokens\":141,\"cost\":0.001383,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.001383,\"upstream_inference_prompt_cost\":0.000183,\"upstream_inference_completions_cost\":0.0012},\"completion_tokens_details\":{\"reasoning_tokens\":73,\"image_tokens\":0,\"audio_tokens\":0}}}\n\ndata: [DONE]\n\n"
|
||||
"body": ": OPENROUTER PROCESSING\n\n: OPENROUTER PROCESSING\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"173\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"173\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\n: OPENROUTER PROCESSING\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\" × 219\\n\\n173 × 200 = 34,600\\n173 × 19 = 173 × 20 - 173 = 3,460 - 173 = 3,287\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\" × 219\\n\\n173 × 200 = 34,600\\n173 × 19 = 173 × 20 - 173 = 3,460 - 173 = 3,287\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":\"\\n\\n34,600 + 3,287 = 37,887\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"text\":\"\\n\\n34,600 + 3,287 = 37,887\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning_details\":[{\"type\":\"reasoning.text\",\"signature\":\"EtgCCosBCA8YAipA0W4viH3kgBs43Cl5ewwVBPXTQElvzfbA2TLF4iSbKy9ZZDCSDjjAlF3Bs4ELEnP3vrrTuTioC6OB380lXQdyIDIRY2xhdWRlLXNvbm5ldC00LTY4AEIIdGhpbmtpbmdaJDRjMGYwNDZmLTI1ZmQtNDVmYi1iZmIzLWEwOGE4ZTI0OWNhNxIMMiUlJC3x/5p5PuTwGgwlc8eipZyoM94BHwMiMO45uQx/ymeOjbugi7RDVPZ4jZXSIiEbVi2CD7zPjAK5fFQoVGP1HD55v9CER823JCp6Dg5Xb7Lrk6NUd1XN2KTKrttK7mATE+IBrDTFmor/1cNeg+9gjIbxM/jn/6L5HPmh3/esEVu24Q0IGLZVoE7cTgGgxsrceKMD71Jp2XQgIWD8ltsPfWw3gSc4p+z18UuPN6LuR0mHHENTnClHrAPnOrxbDIl4ZwZgMX8YAQ==\",\"format\":\"anthropic-claude-v1\",\"index\":0}]},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"37887\",\"role\":\"assistant\"},\"finish_reason\":null,\"native_finish_reason\":null}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\",\"reasoning\":null},\"finish_reason\":\"stop\",\"native_finish_reason\":\"end_turn\"}]}\n\ndata: {\"id\":\"gen-1784374117-AXXPsQRoclZeQGx2uHeK\",\"object\":\"chat.completion.chunk\",\"created\":1784374117,\"model\":\"anthropic/claude-sonnet-4.6\",\"provider\":\"Anthropic\",\"service_tier\":\"default\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"\",\"role\":\"assistant\"},\"finish_reason\":\"stop\",\"native_finish_reason\":\"end_turn\"}],\"usage\":{\"prompt_tokens\":61,\"completion_tokens\":80,\"total_tokens\":141,\"cost\":0.001383,\"is_byok\":false,\"prompt_tokens_details\":{\"cached_tokens\":0,\"cache_write_tokens\":0,\"audio_tokens\":0,\"video_tokens\":0},\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"cost_details\":{\"upstream_inference_cost\":0.001383,\"upstream_inference_prompt_cost\":0.000183,\"upstream_inference_completions_cost\":0.0012},\"completion_tokens_details\":{\"reasoning_tokens\":29,\"image_tokens\":0,\"audio_tokens\":0}}}\n\ndata: [DONE]\n\n"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -282,6 +282,86 @@ describe("provider error classification", () => {
|
||||
).toEqual(Array(6).fill("QuotaExceeded"))
|
||||
})
|
||||
|
||||
test("classifies Z.ai plan and balance limits as quota rather than throttling", () => {
|
||||
const zai = (code: string, message: string) => ({ error: { code, message } })
|
||||
const cases = [
|
||||
zai("1113", "Insufficient balance or no resource package. Please recharge."),
|
||||
zai("1308", "Usage limit reached for 5 hours. Your limit will reset at 2026-10-01 00:00:00"),
|
||||
zai(
|
||||
"1309",
|
||||
"Your GLM Coding Plan package has expired and is temporarily unavailable. You can resume using it after renewing the subscription on the official website.",
|
||||
),
|
||||
zai("1310", "Weekly/Monthly Limit Exhausted. Your limit will reset at 2026-10-01 00:00:00"),
|
||||
zai("1311", "Your current subscription plan does not yet include access to glm-5"),
|
||||
zai("1314", "Your enterprise package has expired. Please contact your enterprise administrator."),
|
||||
zai(
|
||||
"1313",
|
||||
"Your account's current usage pattern does not comply with the Fair Usage Policy, and your request frequency has been limited. For details, please refer to the Subscription Service Agreement. To restore access, please submit a request.",
|
||||
),
|
||||
// Z.ai's Anthropic-compatible endpoint wraps the code and request ID into the message.
|
||||
{
|
||||
type: "error",
|
||||
error: {
|
||||
type: "rate_limit_error",
|
||||
code: "1309",
|
||||
message:
|
||||
"[1309][Your GLM Coding Plan package has expired and is temporarily unavailable. You can resume using it after renewing the subscription on the official website.][20260929132151e73af01340d54b58]",
|
||||
},
|
||||
},
|
||||
]
|
||||
expect(
|
||||
cases.map(
|
||||
(body) =>
|
||||
classifyProviderFailure({ message: body.error.message, status: 429, rawBody: JSON.stringify(body) })._tag,
|
||||
),
|
||||
).toEqual(Array(cases.length).fill("QuotaExceeded"))
|
||||
})
|
||||
|
||||
test("classifies Z.ai prompt length rejections as context overflow", () => {
|
||||
const cases = [
|
||||
{ error: { code: "1261", message: "Prompt 超长" } },
|
||||
{ error: { code: "1261", message: "Prompt too long" } },
|
||||
{
|
||||
type: "error",
|
||||
error: { type: "invalid_request_error", code: "1261", message: "[1261][Prompt too long][2026092913]" },
|
||||
},
|
||||
]
|
||||
expect(
|
||||
cases.map((body) => {
|
||||
const reason = classifyProviderFailure({
|
||||
message: body.error.message,
|
||||
status: 400,
|
||||
rawBody: JSON.stringify(body),
|
||||
})
|
||||
return reason._tag === "InvalidRequest" ? reason.classification : reason._tag
|
||||
}),
|
||||
).toEqual(["context-overflow", "context-overflow", "context-overflow"])
|
||||
})
|
||||
|
||||
test("classifies Z.ai sensitive content rejections as content policy", () => {
|
||||
const message =
|
||||
"System detected potentially unsafe or sensitive content in input or generation. Please avoid using prompts that may generate sensitive content. Thank you for your cooperation."
|
||||
expect(
|
||||
classifyProviderFailure({
|
||||
message,
|
||||
status: 400,
|
||||
rawBody: JSON.stringify({ error: { code: "1301", message } }),
|
||||
})._tag,
|
||||
).toBe("ContentPolicy")
|
||||
})
|
||||
|
||||
test("keeps Z.ai throttling and overload retryable", () => {
|
||||
expect(
|
||||
[
|
||||
{ error: { code: "1302", message: "Rate limit reached for requests" } },
|
||||
{ error: { code: "1305", message: "The service may be temporarily overloaded, please try again later" } },
|
||||
].map(
|
||||
(body) =>
|
||||
classifyProviderFailure({ message: body.error.message, status: 429, rawBody: JSON.stringify(body) })._tag,
|
||||
),
|
||||
).toEqual(["RateLimit", "RateLimit"])
|
||||
})
|
||||
|
||||
test("does not let substituted server codes make a 4xx retryable", () => {
|
||||
const openai = { error: { type: "server_error", message: "Upstream request failed: Model is unavailable." } }
|
||||
const anthropic = {
|
||||
|
||||
@@ -33,12 +33,7 @@ it.effect("Meta selects Messages and lowers native search alongside ordinary fun
|
||||
output_config: { effort: "low" },
|
||||
tools: [
|
||||
{ type: "web_search", name: "web_search", user_location: { type: "approximate", country: "US" } },
|
||||
{
|
||||
name: "lookup",
|
||||
description: "Lookup",
|
||||
input_schema: { type: "object" },
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
{ name: "lookup", description: "Lookup", input_schema: { type: "object" } },
|
||||
],
|
||||
})
|
||||
const entrypoint = yield* Effect.promise(() => import("@opencode/ai/providers/meta/messages"))
|
||||
|
||||
@@ -303,8 +303,7 @@ describe("Mistral Chat", () => {
|
||||
fixedResponse(
|
||||
sseEvents(
|
||||
chunk({ content: [{ type: "thinking", thinking: [], marker: "empty" }] }),
|
||||
chunk({ content: [{ type: "thinking", thinking: [{ type: "text", text: "Con" }] }] }),
|
||||
chunk({ content: [{ type: "thinking", thinking: [{ type: "text", text: "sider" }], closed: true }] }),
|
||||
chunk({ content: [{ type: "thinking", thinking: [{ type: "text", text: "Consider" }] }] }),
|
||||
chunk({ content: [{ type: "text", text: "Answer" }] }),
|
||||
chunk({}, "stop"),
|
||||
),
|
||||
@@ -314,11 +313,6 @@ describe("Mistral Chat", () => {
|
||||
|
||||
expect(response.reasoning).toBe("Consider")
|
||||
expect(response.text).toBe("Answer")
|
||||
expect(response.events.find(LLMEvent.is.reasoningStart)?.providerMetadata).toBeUndefined()
|
||||
expect(response.events.filter(LLMEvent.is.reasoningDelta).map((event) => [event.text, event.providerMetadata])).toEqual([
|
||||
["Con", undefined],
|
||||
["sider", undefined],
|
||||
])
|
||||
expect(response.message.content).toEqual([
|
||||
{
|
||||
type: "reasoning",
|
||||
@@ -327,12 +321,8 @@ describe("Mistral Chat", () => {
|
||||
mistral: {
|
||||
thinking: {
|
||||
type: "thinking",
|
||||
thinking: [
|
||||
{ type: "text", text: "Con" },
|
||||
{ type: "text", text: "sider" },
|
||||
],
|
||||
thinking: [{ type: "text", text: "Consider" }],
|
||||
marker: "empty",
|
||||
closed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -347,12 +337,8 @@ describe("Mistral Chat", () => {
|
||||
content: [
|
||||
{
|
||||
type: "thinking",
|
||||
thinking: [
|
||||
{ type: "text", text: "Con" },
|
||||
{ type: "text", text: "sider" },
|
||||
],
|
||||
thinking: [{ type: "text", text: "Consider" }],
|
||||
marker: "empty",
|
||||
closed: true,
|
||||
},
|
||||
{ type: "text", text: "Answer" },
|
||||
],
|
||||
@@ -371,8 +357,6 @@ describe("Mistral Chat", () => {
|
||||
),
|
||||
),
|
||||
)
|
||||
expect(response.events.find(LLMEvent.is.reasoningStart)?.providerMetadata).toBeUndefined()
|
||||
expect(response.events.filter(LLMEvent.is.reasoningDelta)).toEqual([])
|
||||
expect(response.message.content).toEqual([
|
||||
{
|
||||
type: "reasoning",
|
||||
|
||||
@@ -89,14 +89,7 @@ for (const item of cases) {
|
||||
|
||||
const replay = yield* compileRequest(LLM.request({ model: item.model, messages: [response.message] }))
|
||||
expect(replay.body.messages).toMatchObject([
|
||||
{
|
||||
role: "assistant",
|
||||
content:
|
||||
item.model.route.id === "openrouter"
|
||||
? [{ type: "text", text: response.text, cache_control: { type: "ephemeral" } }]
|
||||
: response.text,
|
||||
reasoning: response.reasoning,
|
||||
},
|
||||
{ role: "assistant", content: response.text, reasoning: response.reasoning },
|
||||
])
|
||||
const replayDetails =
|
||||
replay.body.messages[0]?.role === "assistant" ? replay.body.messages[0].reasoning_details : undefined
|
||||
|
||||
@@ -4996,42 +4996,6 @@ describe("OpenAI Responses route", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("retains the token-sharing HTTP 429 body for retry hooks", () =>
|
||||
Effect.gen(function* () {
|
||||
const body = JSON.stringify({ error: { code: "subscription_sharing_usage_limit_exceeded", message: "Rate limit exceeded" } })
|
||||
const error = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(body, { status: 429, headers: { "content-type": "application/json" } }),
|
||||
),
|
||||
Effect.flip,
|
||||
)
|
||||
|
||||
expect(error).toMatchObject({ reason: { _tag: "RateLimit", http: { status: 429 }, body } })
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("retains the token-sharing response.failed event for retry hooks", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* LLMClient.generate(request).pipe(
|
||||
Effect.provide(
|
||||
fixedResponse(
|
||||
sseEvents({
|
||||
type: "response.failed",
|
||||
response: {
|
||||
id: "resp_usage_limit",
|
||||
error: { code: "subscription_sharing_usage_limit_exceeded", message: "Rate limit exceeded" },
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.flip,
|
||||
)
|
||||
|
||||
expect(error).toMatchObject({ reason: { _tag: "RateLimit" } })
|
||||
expect(error.reason.body).toContain("subscription_sharing_usage_limit_exceeded")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("surfaces response.failed code when no nested message is present", () =>
|
||||
Effect.gen(function* () {
|
||||
const error = yield* LLMClient.generate(request).pipe(
|
||||
|
||||
@@ -32,125 +32,6 @@ describe("OpenRouter", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("places default cache breakpoints on tools, system boundaries, and the conversation tail", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: OpenRouter.configure({ apiKey: "test-key" }).model("anthropic/claude-sonnet-4.6"),
|
||||
system: [
|
||||
{ type: "text", text: "Base agent" },
|
||||
{ type: "text", text: "Model details" },
|
||||
{ type: "text", text: "Project instructions" },
|
||||
],
|
||||
tools: [
|
||||
{ name: "read", description: "Read", inputSchema: { type: "object", properties: {} } },
|
||||
{ name: "lookup", description: "Lookup", inputSchema: { type: "object", properties: {} } },
|
||||
],
|
||||
prompt: "Hello",
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body.tools?.map((tool) => tool.cache_control)).toEqual([undefined, { type: "ephemeral" }])
|
||||
expect(prepared.body.messages).toMatchObject([
|
||||
{
|
||||
role: "system",
|
||||
content: [
|
||||
{ text: "Base agent", cache_control: { type: "ephemeral" } },
|
||||
{ text: "Model details" },
|
||||
{ text: "Project instructions", cache_control: { type: "ephemeral" } },
|
||||
],
|
||||
},
|
||||
{ role: "user", content: [{ text: "Hello", cache_control: { type: "ephemeral" } }] },
|
||||
])
|
||||
expect(prepared.body.messages[0]?.content).not.toContainEqual(
|
||||
expect.objectContaining({ text: "Model details", cache_control: expect.anything() }),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("places default cache breakpoints on OpenRouter latest-model aliases", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: OpenRouter.configure({ apiKey: "test-key" }).model("~anthropic/claude-sonnet-latest"),
|
||||
system: "Base agent",
|
||||
tools: [{ name: "lookup", description: "Lookup", inputSchema: { type: "object", properties: {} } }],
|
||||
prompt: "Hello",
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body.tools?.[0]?.cache_control).toEqual({ type: "ephemeral" })
|
||||
expect(prepared.body.messages).toMatchObject([
|
||||
{ role: "system", content: [{ text: "Base agent", cache_control: { type: "ephemeral" } }] },
|
||||
{ role: "user", content: [{ text: "Hello", cache_control: { type: "ephemeral" } }] },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("skips the tool breakpoint for Qwen, which caches tools with the system prompt", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: OpenRouter.configure({ apiKey: "test-key" }).model("qwen/qwen3-coder-plus"),
|
||||
system: "Base agent",
|
||||
tools: [{ name: "lookup", description: "Lookup", inputSchema: { type: "object", properties: {} } }],
|
||||
prompt: "Hello",
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body.tools?.[0]?.cache_control).toBeUndefined()
|
||||
expect(prepared.body.messages).toMatchObject([
|
||||
{ role: "system", content: [{ text: "Base agent", cache_control: { type: "ephemeral" } }] },
|
||||
{ role: "user", content: [{ text: "Hello", cache_control: { type: "ephemeral" } }] },
|
||||
])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("places the default Qwen conversation-tail breakpoint inside tool-result text", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: OpenRouter.configure({ apiKey: "test-key" }).model("qwen/qwen3-coder-plus"),
|
||||
messages: [
|
||||
Message.user("Call the tool"),
|
||||
Message.assistant([{ type: "tool-call", id: "call_1", name: "lookup", input: {} }]),
|
||||
Message.tool({ id: "call_1", name: "lookup", result: "Done" }),
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body.messages.at(-1)).toMatchObject({
|
||||
role: "tool",
|
||||
tool_call_id: "call_1",
|
||||
content: [{ type: "text", text: '"Done"', cache_control: { type: "ephemeral" } }],
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("sends no default breakpoints to upstreams that cache without them", () =>
|
||||
Effect.gen(function* () {
|
||||
const openrouter = OpenRouter.configure({ apiKey: "test-key" })
|
||||
const bodies = yield* Effect.forEach(["google/gemini-2.5-flash", "openai/gpt-5-mini"], (id) =>
|
||||
compileRequest(
|
||||
LLM.request({
|
||||
model: openrouter.model(id),
|
||||
system: "Base agent",
|
||||
tools: [{ name: "lookup", description: "Lookup", inputSchema: { type: "object", properties: {} } }],
|
||||
prompt: "Hello",
|
||||
}),
|
||||
).pipe(Effect.map((prepared) => prepared.body)),
|
||||
)
|
||||
|
||||
bodies.forEach((body) => {
|
||||
expect(body.tools?.[0]?.cache_control).toBeUndefined()
|
||||
expect(body.messages).toMatchObject([
|
||||
{ role: "system", content: "Base agent" },
|
||||
{ role: "user", content: "Hello" },
|
||||
])
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("lowers the native cache policy to OpenRouter cache controls", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
@@ -193,8 +74,6 @@ describe("OpenRouter", () => {
|
||||
cache: "none",
|
||||
messages: [
|
||||
Message.user("Call the tool"),
|
||||
Message.assistant("Unmarked reply"),
|
||||
Message.user("Call again"),
|
||||
Message.assistant([
|
||||
{ type: "text", text: "Calling", cache: new CacheHint({ type: "ephemeral" }) },
|
||||
{ type: "tool-call", id: "call_1", name: "lookup", input: {} },
|
||||
@@ -211,17 +90,8 @@ describe("OpenRouter", () => {
|
||||
|
||||
expect(prepared.body.messages).toMatchObject([
|
||||
{ role: "user", content: "Call the tool" },
|
||||
{ role: "assistant", content: "Unmarked reply" },
|
||||
{ role: "user", content: "Call again" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Calling", cache_control: { type: "ephemeral" } }],
|
||||
tool_calls: [{ id: "call_1", type: "function", function: { name: "lookup", arguments: "{}" } }],
|
||||
},
|
||||
{
|
||||
role: "tool",
|
||||
content: [{ type: "text", text: '"Done"', cache_control: { type: "ephemeral", ttl: "1h" } }],
|
||||
},
|
||||
{ role: "assistant", content: "Calling", cache_control: { type: "ephemeral" } },
|
||||
{ role: "tool", content: '"Done"', cache_control: { type: "ephemeral", ttl: "1h" } },
|
||||
])
|
||||
}),
|
||||
)
|
||||
@@ -248,7 +118,7 @@ describe("OpenRouter", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("does not emit text cache markers on reasoning-only assistant messages", () =>
|
||||
it.effect("preserves cache policy hints on reasoning-only assistant messages", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
@@ -260,38 +130,8 @@ describe("OpenRouter", () => {
|
||||
|
||||
expect(prepared.body.messages).toMatchObject([
|
||||
{ role: "user", content: "Think" },
|
||||
{ role: "assistant", content: "" },
|
||||
{ role: "assistant", cache_control: { type: "ephemeral" } },
|
||||
])
|
||||
expect(prepared.body.messages[1]).not.toHaveProperty("cache_control")
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("counts wrapped system-update markers once so all four default breakpoints survive", () =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* compileRequest(
|
||||
LLM.request({
|
||||
model: OpenRouter.configure({ apiKey: "test-key" }).model("anthropic/claude-sonnet-4.6"),
|
||||
system: [
|
||||
{ type: "text", text: "Base agent" },
|
||||
{ type: "text", text: "Project instructions" },
|
||||
],
|
||||
tools: [{ name: "lookup", description: "Lookup", inputSchema: { type: "object", properties: {} } }],
|
||||
messages: [Message.user("Start"), Message.system("Updated instructions")],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(prepared.body.tools?.[0]?.cache_control).toEqual({ type: "ephemeral" })
|
||||
expect(prepared.body.messages.at(-1)).toMatchObject({
|
||||
role: "user",
|
||||
content: [
|
||||
{ type: "text", text: "Start" },
|
||||
{
|
||||
type: "text",
|
||||
text: "<system-update>\nUpdated instructions\n</system-update>",
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
],
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@@ -186,17 +186,11 @@ for (const scenario of scenarios) {
|
||||
const toolTitle = element
|
||||
.closest('[data-component="edit-tool"]')
|
||||
?.querySelector('[data-slot="collapsible-trigger"][data-locked]')
|
||||
// Grouped file headers stack below the stuck Used header.
|
||||
const group = element
|
||||
.closest('[data-component="collapsed-tool-group"]')
|
||||
?.querySelector(':scope > [data-component="collapsible"] > [data-slot="collapsible-trigger"]')
|
||||
const sessionTop = viewport.getBoundingClientRect().top + (title?.getBoundingClientRect().height ?? 0)
|
||||
const top = sessionTop + (group?.getBoundingClientRect().height ?? 0)
|
||||
const top = viewport.getBoundingClientRect().top + (title?.getBoundingClientRect().height ?? 0)
|
||||
const rect = element.getBoundingClientRect()
|
||||
const trigger = element.querySelector("button")!
|
||||
return {
|
||||
gap: Math.abs(rect.top - top - (toolTitle?.getBoundingClientRect().height ?? 0)),
|
||||
groupGap: group ? Math.abs(group.getBoundingClientRect().top - sessionTop) : 0,
|
||||
titleGap: toolTitle ? Math.abs(toolTitle.getBoundingClientRect().top - top) : 0,
|
||||
clickable: trigger.contains(
|
||||
document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2),
|
||||
@@ -204,7 +198,7 @@ for (const scenario of scenarios) {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.toEqual({ gap: 0, groupGap: 0, titleGap: 0, clickable: true })
|
||||
.toEqual({ gap: 0, titleGap: 0, clickable: true })
|
||||
await page.screenshot({ path: info.outputPath(`${file}.png`) })
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/app",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { onMount } from "solid-js"
|
||||
import { DialogChatGPTUsageLimit } from "./chatgpt-usage-limit"
|
||||
|
||||
function UsageLimitStory() {
|
||||
const dialog = useDialog()
|
||||
const open = () => dialog.show(() => <DialogChatGPTUsageLimit />)
|
||||
onMount(open)
|
||||
|
||||
return (
|
||||
<Button variant="neutral" onClick={open}>
|
||||
Open ChatGPT usage limit
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
title: "App/Dialogs/Connect Provider",
|
||||
id: "app-dialog-chatgpt-usage-limit",
|
||||
}
|
||||
|
||||
export const ChatGPTUsageLimit = { render: () => <UsageLimitStory /> }
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { Dialog, DialogTitle } from "@opencode/ui/dialog"
|
||||
import { ProviderIcon } from "@opencode/ui/provider-icon"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { usePlatform } from "@/runtime/platform/platform"
|
||||
|
||||
export function DialogChatGPTUsageLimit() {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
const platform = usePlatform()
|
||||
|
||||
return (
|
||||
<Dialog fit containerClass="!w-[min(calc(100vw_-_32px),390px)] !rounded-xl">
|
||||
<div class="flex w-full flex-col items-center px-8 pb-8 pt-9 text-center [font-family:var(--v2-font-family-sans)]">
|
||||
<ProviderIcon id="openai" class="!size-12 text-v2-icon-icon-base" aria-hidden="true" />
|
||||
<div class="mt-6 max-w-[270px] text-[20px] font-[530] leading-7 tracking-[-0.3px] text-v2-text-text-base">
|
||||
<DialogTitle>
|
||||
<bdi dir="auto">{language.t("provider.connect.chatgptUsageLimit.title")}</bdi>
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<p class="mt-3 text-[13px] leading-5 text-v2-text-text-muted">
|
||||
<bdi dir="auto">{language.t("provider.connect.chatgptUsageLimit.description")}</bdi>
|
||||
</p>
|
||||
<Button
|
||||
variant="contrast"
|
||||
size="large"
|
||||
class="mt-8 w-full"
|
||||
autofocus
|
||||
onClick={() => {
|
||||
platform.openExternal("https://chatgpt.com/settings/usage")
|
||||
dialog.close()
|
||||
}}
|
||||
>
|
||||
{language.t("provider.connect.chatgptUsageLimit.manage")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="large"
|
||||
class="mt-2 w-full"
|
||||
onClick={() => dialog.close()}
|
||||
>
|
||||
{language.t("provider.connect.chatgptUsageLimit.close")}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { onMount } from "solid-js"
|
||||
import { DialogChatGPTPlanWelcome } from "./chatgpt-welcome"
|
||||
|
||||
function WelcomeStory() {
|
||||
const dialog = useDialog()
|
||||
const open = () => dialog.show(() => <DialogChatGPTPlanWelcome />)
|
||||
onMount(open)
|
||||
|
||||
return (
|
||||
<Button variant="neutral" onClick={open}>
|
||||
Open ChatGPT plan welcome
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
title: "App/Dialogs/Connect Provider",
|
||||
id: "app-dialog-chatgpt-welcome",
|
||||
}
|
||||
|
||||
export const ChatGPTPlanWelcome = { render: () => <WelcomeStory /> }
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { useDialog } from "@opencode/ui/context/dialog"
|
||||
import { Dialog, DialogTitle } from "@opencode/ui/dialog"
|
||||
import { ProviderIcon } from "@opencode/ui/provider-icon"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { ExternalLink } from "@/runtime/platform/external-link"
|
||||
|
||||
export function DialogChatGPTPlanWelcome() {
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
|
||||
return (
|
||||
<Dialog fit containerClass="!w-[min(calc(100vw_-_32px),390px)] !rounded-xl">
|
||||
<div class="flex w-full flex-col items-center px-8 pb-8 pt-9 text-center [font-family:var(--v2-font-family-sans)]">
|
||||
<ProviderIcon id="openai" class="!size-12 text-v2-icon-icon-base" aria-hidden="true" />
|
||||
<div class="mt-6 max-w-[270px] text-[20px] font-[530] leading-7 tracking-[-0.3px] text-v2-text-text-base">
|
||||
<DialogTitle>
|
||||
<bdi dir="auto">{language.t("provider.connect.chatgptWelcome.title")}</bdi>
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<p class="mt-3 text-[13px] leading-5 text-v2-text-text-muted">
|
||||
<bdi dir="auto">{language.t("provider.connect.chatgptWelcome.description")}</bdi>
|
||||
</p>
|
||||
<ExternalLink
|
||||
href="https://chatgpt.com/settings/usage"
|
||||
dir="auto"
|
||||
class="mt-1 rounded-sm text-[13px] leading-5 text-v2-text-text-muted underline-offset-2 hover:text-v2-text-text-base focus-visible:outline focus-visible:outline-2"
|
||||
>
|
||||
{language.t("provider.connect.chatgptWelcome.usage")}
|
||||
</ExternalLink>
|
||||
<Button variant="contrast" size="large" class="mt-8 w-full" autofocus onClick={() => dialog.close()}>
|
||||
{language.t("provider.connect.chatgptWelcome.confirm")}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -39,9 +39,6 @@ import { OpenCodeLogo } from "@/providers/opencode-logo"
|
||||
import { decode64 } from "@/runtime/persistence/base64"
|
||||
import { SettingsList } from "@/settings/list"
|
||||
import { useTabs } from "@/shell/tabs/tabs"
|
||||
import { Persist, persisted } from "@/runtime/persistence/storage"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
import { Schema } from "effect"
|
||||
import {
|
||||
CONSOLE_INTEGRATION,
|
||||
CONSOLE_PROVIDERS,
|
||||
@@ -51,7 +48,6 @@ import {
|
||||
type ProviderConnectMethod,
|
||||
} from "./controller"
|
||||
import { ConsoleAuthorization } from "./console"
|
||||
import { DialogChatGPTPlanWelcome } from "./chatgpt-welcome"
|
||||
import { authServerName, RemoteAuthNotice } from "./remote"
|
||||
import "./models.css"
|
||||
|
||||
@@ -85,15 +81,8 @@ export const DialogConnectProvider: Component<{
|
||||
completed: false,
|
||||
modelProvider: undefined as { id: string; name: string } | undefined,
|
||||
authorization: false,
|
||||
chatgptWelcome: false,
|
||||
})
|
||||
const language = useLanguage()
|
||||
const dialog = useDialog()
|
||||
const [welcome, setWelcome, , welcomeReady] = persisted(
|
||||
Persist.global("chatgpt-plan-welcome.v1"),
|
||||
Persistence.struct({ seen: Schema.Boolean }),
|
||||
{ seen: false },
|
||||
)
|
||||
const reset = controller.reset
|
||||
const back = { current: reset }
|
||||
const consoleSelected = () => CONSOLE_PROVIDERS.has(controller.selected() ?? "")
|
||||
@@ -123,11 +112,7 @@ export const DialogConnectProvider: Component<{
|
||||
setBack={(handler) => (back.current = handler)}
|
||||
selection={props.selection}
|
||||
onDone={props.onDone ? () => setState("completed", true) : undefined}
|
||||
onConnected={(methodID) => {
|
||||
props.onConnected?.(provider)
|
||||
if (provider === "openai" && methodID === "chatgpt-token-sharing")
|
||||
setState("chatgptWelcome", true)
|
||||
}}
|
||||
onConnected={() => props.onConnected?.(provider)}
|
||||
onFirstConnection={(provider) => setState("modelProvider", provider)}
|
||||
onAuthorization={(authorization) => setState("authorization", authorization)}
|
||||
/>
|
||||
@@ -151,16 +136,9 @@ export const DialogConnectProvider: Component<{
|
||||
: "!h-[min(calc(100vh_-_16px),512px)] !w-[min(calc(100vw_-_16px),640px)]"
|
||||
}
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (state.completed && props.onDone) {
|
||||
event.preventDefault()
|
||||
props.onDone()
|
||||
}
|
||||
if (!state.chatgptWelcome) return
|
||||
void Promise.resolve(welcomeReady.promise).then(() => {
|
||||
if (welcome.seen) return
|
||||
setWelcome("seen", true)
|
||||
void dialog.show(() => <DialogChatGPTPlanWelcome />)
|
||||
})
|
||||
if (!state.completed || !props.onDone) return
|
||||
event.preventDefault()
|
||||
props.onDone()
|
||||
}}
|
||||
class="[font-family:var(--v2-font-family-sans)] [&_[data-slot=dialog-header]]:!px-5 [&_[data-slot=dialog-header-title]]:!text-[15px] [&_[data-slot=dialog-header-title]]:!tracking-[-0.13px]"
|
||||
classList={{
|
||||
@@ -376,7 +354,7 @@ function ProviderConnection(props: {
|
||||
setBack: (handler: () => void) => void
|
||||
selection?: ModelSelection
|
||||
onDone?: () => void
|
||||
onConnected?: (methodID?: string) => void
|
||||
onConnected?: () => void
|
||||
onFirstConnection: (provider: { id: string; name: string }) => void
|
||||
onAuthorization: (authorization: boolean) => void
|
||||
}) {
|
||||
@@ -423,8 +401,7 @@ function ProviderConnection(props: {
|
||||
prepare: isConsole ? prepareConsoleCatalog : undefined,
|
||||
pollInterval: isConsole ? 500 : undefined,
|
||||
onComplete: () => {
|
||||
const method = controller.currentMethod()
|
||||
props.onConnected?.(method?.type === "oauth" ? method.id : undefined)
|
||||
props.onConnected?.()
|
||||
// The picker only lists the newest model per family by default, which hides most of
|
||||
// what a new connection just unlocked. Show everything the connected integration offers.
|
||||
global.models.show(
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Button } from "@opencode/ui/button"
|
||||
import { createSignal } from "solid-js"
|
||||
import type { ModelSelection } from "./selection"
|
||||
import { ModelSelectorPopoverView } from "./select-dialog"
|
||||
|
||||
const chatgpt = {
|
||||
id: "gpt-5.6-sol",
|
||||
providerID: "openai",
|
||||
api: { id: "gpt-5.6-sol", url: "https://api.openai.com/v1", npm: "@opencode/ai/providers/openai" },
|
||||
name: "GPT-5.6 Sol",
|
||||
family: "gpt",
|
||||
capabilities: {
|
||||
temperature: true,
|
||||
reasoning: true,
|
||||
attachment: true,
|
||||
toolcall: true,
|
||||
input: { text: true, audio: false, image: true, video: false, pdf: false },
|
||||
output: { text: true, audio: false, image: false, video: false, pdf: false },
|
||||
interleaved: true,
|
||||
},
|
||||
cost: { input: 0, output: 0, cache: { read: 0, write: 0 } },
|
||||
limit: { context: 400_000, output: 64_000 },
|
||||
status: "active",
|
||||
options: {},
|
||||
headers: {},
|
||||
release_date: "2026-09-01",
|
||||
variants: {},
|
||||
provider: {
|
||||
id: "openai",
|
||||
name: "OpenAI",
|
||||
source: "custom",
|
||||
env: [],
|
||||
options: {},
|
||||
models: {},
|
||||
},
|
||||
latest: true,
|
||||
} satisfies NonNullable<ReturnType<ModelSelection["current"]>>
|
||||
|
||||
const models = [
|
||||
chatgpt,
|
||||
{
|
||||
...chatgpt,
|
||||
id: "gpt-5.6-terra",
|
||||
api: { ...chatgpt.api, id: "gpt-5.6-terra" },
|
||||
name: "GPT-5.6 Terra",
|
||||
latest: false,
|
||||
},
|
||||
]
|
||||
|
||||
function SelectorStory(props: { plan: boolean }) {
|
||||
const [current, setCurrent] = createSignal(models[0].id)
|
||||
return (
|
||||
<div class="flex min-h-[280px] items-end justify-center">
|
||||
<ModelSelectorPopoverView
|
||||
trigger={(trigger) => (
|
||||
<Button {...trigger} variant="ghost-muted">
|
||||
{models.find((model) => model.id === current())?.name}
|
||||
</Button>
|
||||
)}
|
||||
models={(search) => models.filter((model) => model.name.toLowerCase().includes(search.toLowerCase()))}
|
||||
groups={(items) => [{ category: "openai", items }]}
|
||||
current={`openai:${current()}`}
|
||||
chatgptPlan={props.plan}
|
||||
select={(item) => setCurrent(item.id)}
|
||||
onManage={() => undefined}
|
||||
onClose={() => undefined}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
title: "App/Dialogs/Model Selector",
|
||||
id: "app-dialog-model-selector",
|
||||
}
|
||||
|
||||
export const ChatGPTPlan = { render: () => <SelectorStory plan /> }
|
||||
export const ApiKey = { render: () => <SelectorStory plan={false} /> }
|
||||
@@ -15,16 +15,13 @@ import { Menu } from "@opencode/ui/menu"
|
||||
import { TextInput } from "@opencode/ui/text-input"
|
||||
import { ModelTooltip } from "./tooltip"
|
||||
import { useLanguage } from "@/runtime/i18n/language"
|
||||
import { ExternalLink } from "@/runtime/platform/external-link"
|
||||
import { useData } from "@/runtime/server/current"
|
||||
import { useWorkspaceLocation } from "@/workspaces/location"
|
||||
import { decode64 } from "@/runtime/persistence/base64"
|
||||
import { handleDocumentSearchKeydown } from "@/shell/commands/search-keydown"
|
||||
import { createMenuDismissController } from "@/shell/commands/menu-dismiss"
|
||||
import { createEventListener } from "@solid-primitives/event-listener"
|
||||
import { matchesModelSearch } from "./search"
|
||||
import { SettingsList } from "@/settings/list"
|
||||
import { CONSOLE_GROUP_KEY, consoleModelGroup, ProviderModelIcon, ProviderModelSections } from "@/providers/models/provider-group"
|
||||
import { CONSOLE_GROUP_KEY, consoleModelGroup, ProviderModelSections } from "@/providers/models/provider-group"
|
||||
import "@/settings/settings.css"
|
||||
|
||||
const isFree = (provider: string, cost: { input: number } | undefined) =>
|
||||
@@ -221,21 +218,11 @@ export function ModelSelectorPopover(props: {
|
||||
onClose?: () => void
|
||||
}) {
|
||||
const dialog = useDialog()
|
||||
const data = useData()
|
||||
const location = useWorkspaceLocation()
|
||||
const controller = createModelSelectorController({
|
||||
model: props.model,
|
||||
provider: () => props.provider,
|
||||
onSelect: () => props.onClose?.(),
|
||||
})
|
||||
const chatgptPlan = () => {
|
||||
if (!controller.current()?.startsWith("openai:")) return false
|
||||
const connection = data.location.integration
|
||||
.list(location().ref)
|
||||
?.find((integration) => integration.id === "openai")
|
||||
?.connections[0]
|
||||
return connection?.type === "credential" && connection.method === "oauth"
|
||||
}
|
||||
|
||||
return (
|
||||
<ModelSelectorPopoverView
|
||||
@@ -243,7 +230,6 @@ export function ModelSelectorPopover(props: {
|
||||
models={controller.models}
|
||||
groups={controller.groups}
|
||||
current={controller.current()}
|
||||
chatgptPlan={chatgptPlan()}
|
||||
select={controller.select}
|
||||
onManage={() => {
|
||||
void import("./manage").then((module) => {
|
||||
@@ -295,12 +281,11 @@ function createModelSelectorController(input: {
|
||||
}
|
||||
}
|
||||
|
||||
export function ModelSelectorPopoverView(props: {
|
||||
function ModelSelectorPopoverView(props: {
|
||||
trigger: ModelSelectorTrigger
|
||||
models: (search: string) => ModelItem[]
|
||||
groups: (models: ModelItem[]) => { category: string; items: ModelItem[] }[]
|
||||
current: string | undefined
|
||||
chatgptPlan?: boolean
|
||||
select: (item: ModelItem) => void
|
||||
onManage: () => void
|
||||
onClose: () => void
|
||||
@@ -383,8 +368,7 @@ export function ModelSelectorPopoverView(props: {
|
||||
<Menu.Portal>
|
||||
<Menu.Content
|
||||
ref={(element: HTMLDivElement) => (contentRef = element)}
|
||||
class="w-[284px] max-w-[calc(100vw-16px)] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 !p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none"
|
||||
classList={{ "!w-[320px]": props.chatgptPlan }}
|
||||
class="w-[284px] overflow-hidden rounded-md border-0 bg-v2-background-bg-layer-01 !p-0 shadow-[var(--v2-elevation-floating)] focus:outline-none"
|
||||
onPointerDownOutside={dismiss.preventTriggerRestore}
|
||||
onFocusOutside={dismiss.preventTriggerRestore}
|
||||
onCloseAutoFocus={dismiss.onCloseAutoFocus}
|
||||
@@ -521,20 +505,6 @@ export function ModelSelectorPopoverView(props: {
|
||||
<span class="min-w-0 flex-1 truncate leading-5">{language.t("dialog.model.manage")}</span>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
<Show when={props.chatgptPlan}>
|
||||
<div class="h-px bg-v2-border-border-muted" />
|
||||
<div class="flex min-h-10 items-center gap-2 px-3 py-2 text-[13px] leading-5 text-v2-text-text-base">
|
||||
<ProviderModelIcon provider={{ id: "openai", name: "OpenAI" }} class="shrink-0" />
|
||||
<span class="min-w-0 flex-1 truncate">{language.t("dialog.model.chatgptPlan")}</span>
|
||||
<ExternalLink
|
||||
href="https://chatgpt.com/settings/usage"
|
||||
class="flex shrink-0 items-center gap-1 rounded-sm text-v2-text-text-muted no-underline hover:text-v2-text-text-base focus-visible:outline focus-visible:outline-2"
|
||||
>
|
||||
{language.t("dialog.model.chatgptManageUsage")}
|
||||
<Icon name="arrow-up-right" size="small" />
|
||||
</ExternalLink>
|
||||
</div>
|
||||
</Show>
|
||||
</Menu.Content>
|
||||
</Menu.Portal>
|
||||
</Menu>
|
||||
|
||||
@@ -186,8 +186,6 @@ export const dict = {
|
||||
"dialog.model.empty": "No model results",
|
||||
"dialog.model.manage": "Manage models",
|
||||
"dialog.model.manage.description": "Customize which models appear in the model selector.",
|
||||
"dialog.model.chatgptPlan": "Using ChatGPT plan",
|
||||
"dialog.model.chatgptManageUsage": "Manage usage",
|
||||
"dialog.model.manage.provider.toggle": "Toggle all {{provider}} models",
|
||||
|
||||
"dialog.model.unpaid.freeModels.title": "Free models provided by OpenCode",
|
||||
@@ -219,15 +217,7 @@ export const dict = {
|
||||
"provider.connect.models.title": "Connected to {{provider}}",
|
||||
"provider.connect.models.description": "Choose a model to start with. You can switch models anytime.",
|
||||
"provider.connect.models.available": "Available models",
|
||||
"provider.connect.chatgptWelcome.title": "ChatGPT connected",
|
||||
"provider.connect.chatgptWelcome.description": "Eligible requests in OpenCode can use your ChatGPT plan.",
|
||||
"provider.connect.chatgptWelcome.usage": "Manage usage in ChatGPT settings",
|
||||
"provider.connect.chatgptWelcome.confirm": "Got it",
|
||||
"provider.connect.models.list": "Models available from {{provider}}",
|
||||
"provider.connect.chatgptUsageLimit.title": "ChatGPT usage limit reached",
|
||||
"provider.connect.chatgptUsageLimit.description": "Check your usage and limits in ChatGPT settings.",
|
||||
"provider.connect.chatgptUsageLimit.manage": "Manage usage",
|
||||
"provider.connect.chatgptUsageLimit.close": "Close",
|
||||
"provider.connect.console.refreshFailed":
|
||||
"Your account is connected, but we couldn't load your models. Try again to refresh them.",
|
||||
"provider.connect.console.connected": "OpenCode connected",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Schema } from "effect"
|
||||
import { GoUpsellState, isChatGPTUsageLimit } from "./usage-exceeded-dialogs"
|
||||
import { GoUpsellState } from "./usage-exceeded-dialogs"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
|
||||
const decode = Schema.decodeUnknownSync(
|
||||
@@ -38,31 +38,3 @@ describe("usage exceeded preferences", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("ChatGPT usage limit", () => {
|
||||
const message =
|
||||
"ChatGPT usage limit reached. Try again after your allowance resets; check ChatGPT Settings → Usage for details."
|
||||
|
||||
test("detects the mapped message regardless of failure type", () => {
|
||||
expect(
|
||||
isChatGPTUsageLimit({
|
||||
type: "provider.rate-limit",
|
||||
message,
|
||||
status: 429,
|
||||
}),
|
||||
).toBe(true)
|
||||
expect(isChatGPTUsageLimit({ type: "provider.unknown", message })).toBe(true)
|
||||
})
|
||||
|
||||
test("ignores other rate limits and similar messages", () => {
|
||||
expect(isChatGPTUsageLimit({ type: "provider.rate-limit", message: "Rate limit exceeded", status: 429 })).toBe(
|
||||
false,
|
||||
)
|
||||
expect(
|
||||
isChatGPTUsageLimit({
|
||||
type: "provider.rate-limit",
|
||||
message: "ChatGPT usage limit reached",
|
||||
}),
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useWorkspaceLocation } from "@/workspaces/location"
|
||||
import { Persist, persisted } from "@/runtime/persistence/storage"
|
||||
import type { SessionStatus, SessionStepFailed } from "@opencode/client/promise"
|
||||
import type { SessionStatus } from "@opencode/client/promise"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { Schema } from "effect"
|
||||
import { Persistence } from "@/runtime/persistence/schema"
|
||||
@@ -14,14 +14,6 @@ const GO_UPSELL_ACCOUNT_RATE_LIMIT_LAST_SEEN_AT = "go_upsell_account_rate_limit_
|
||||
const GO_UPSELL_ACCOUNT_RATE_LIMIT_DONT_SHOW = "go_upsell_account_rate_limit_dont_show"
|
||||
const GO_UPSELL_WINDOW = 86_400_000 // 24 hrs
|
||||
const GO_UPSELL_PROVIDERS = new Set(["opencode", "opencode-go"])
|
||||
const CHATGPT_USAGE_LIMIT_WINDOW = 86_400_000 // 24 hrs
|
||||
|
||||
export function isChatGPTUsageLimit(error: SessionStepFailed["data"]["error"]) {
|
||||
return (
|
||||
error.message ===
|
||||
"ChatGPT usage limit reached. Try again after your allowance resets; check ChatGPT Settings → Usage for details."
|
||||
)
|
||||
}
|
||||
|
||||
export const GoUpsellState = Persistence.struct({
|
||||
[GO_UPSELL_FREE_TIER_LAST_SEEN_AT]: Schema.NullOr(Schema.Finite),
|
||||
@@ -60,25 +52,6 @@ export function useUsageExceededDialogs() {
|
||||
[GO_UPSELL_ACCOUNT_RATE_LIMIT_LAST_SEEN_AT]: null,
|
||||
[GO_UPSELL_ACCOUNT_RATE_LIMIT_DONT_SHOW]: null,
|
||||
})
|
||||
const [chatgptUsageLimit, setChatGPTUsageLimit] = persisted(
|
||||
Persist.global("chatgpt-usage-limit"),
|
||||
Persistence.struct({ lastSeenAt: Schema.NullOr(Schema.Finite) }),
|
||||
{ lastSeenAt: null },
|
||||
)
|
||||
|
||||
onCleanup(
|
||||
sdk().event.on("session.step.failed", (evt) => {
|
||||
if (evt.data.sessionID !== params.id) return
|
||||
if (!isChatGPTUsageLimit(evt.data.error) || dialog.active) return
|
||||
if (chatgptUsageLimit.lastSeenAt && Date.now() - chatgptUsageLimit.lastSeenAt < CHATGPT_USAGE_LIMIT_WINDOW) return
|
||||
|
||||
void import("@/providers/connect/chatgpt-usage-limit").then((usage) => {
|
||||
if (dialog.active) return
|
||||
setChatGPTUsageLimit("lastSeenAt", Date.now())
|
||||
dialog.show(() => <usage.DialogChatGPTUsageLimit />)
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
onCleanup(
|
||||
sdk().event.on("session.status", (evt) => {
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import { afterAll, beforeAll, expect, mock, test } from "bun:test"
|
||||
import { createRoot } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import type { ServerSDK } from "@/runtime/server/client"
|
||||
import type { Data } from "@opencode/client/solid"
|
||||
import type { ServerConnection } from "@/runtime/server/registry"
|
||||
import { ServerScope } from "@/runtime/server/scope"
|
||||
import type { Tab } from "@/shell/tabs/tabs"
|
||||
|
||||
const server = "local\nhttp://localhost:4096" as ServerConnection.Key
|
||||
const session = { id: "session-1", title: "Test session", location: { directory: "/project" } }
|
||||
const alerts: string[] = []
|
||||
const tabs: { store: Tab[] } = { store: [] }
|
||||
let createServerNotificationState: typeof import("./notification").createServerNotificationState
|
||||
let storage: typeof import("@/runtime/persistence/storage")
|
||||
|
||||
beforeAll(async () => {
|
||||
storage = await import("@/runtime/persistence/storage")
|
||||
const { sessionIDHasOpenTab } = await import("@/shell/tabs/tabs")
|
||||
mock.module("@/runtime/platform/platform", () => ({
|
||||
usePlatform: () => ({
|
||||
platform: "web",
|
||||
notify: async (title: string) => {
|
||||
alerts.push(title)
|
||||
},
|
||||
}),
|
||||
}))
|
||||
mock.module("@/settings/model", () => ({
|
||||
useSettings: () => ({
|
||||
sounds: { agentEnabled: () => false, errorsEnabled: () => false },
|
||||
notifications: { agent: () => true, errors: () => true },
|
||||
}),
|
||||
}))
|
||||
mock.module("@/runtime/i18n/language", () => ({ useLanguage: () => ({ t: (key: string) => key }) }))
|
||||
mock.module("@/shell/tabs/tabs", () => ({
|
||||
useTabs: () => tabs,
|
||||
sessionIDHasOpenTab,
|
||||
}))
|
||||
mock.module("@/runtime/persistence/storage", () => ({
|
||||
...storage,
|
||||
persisted: () => {
|
||||
const [store, setStore] = createStore({ list: [] })
|
||||
return [store, setStore, undefined, () => false]
|
||||
},
|
||||
}))
|
||||
createServerNotificationState = (await import("./notification")).createServerNotificationState
|
||||
})
|
||||
|
||||
afterAll(() => mock.module("@/runtime/persistence/storage", () => storage))
|
||||
|
||||
test.each([
|
||||
["session.execution.succeeded", "notification.session.responseReady.title"],
|
||||
["session.execution.failed", "notification.session.error.title"],
|
||||
] as const)("system alert for %s requires an open session tab", async (type, title) => {
|
||||
alerts.length = 0
|
||||
tabs.store = [{ type: "session", server, sessionId: "another-session" }]
|
||||
let listener: ((event: unknown) => void) | undefined
|
||||
const dispose = createRoot((dispose) => {
|
||||
const state = createServerNotificationState({
|
||||
key: server,
|
||||
sdk: {
|
||||
scope: ServerScope.local,
|
||||
event: {
|
||||
listen: (fn: typeof listener) => {
|
||||
listener = fn
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
} as unknown as ServerSDK,
|
||||
data: { session: { get: () => session } } as unknown as Data,
|
||||
coordinator: { system: async (_id: string, fn: () => Promise<void>) => fn() },
|
||||
} as Parameters<typeof createServerNotificationState>[0])
|
||||
return { dispose, state }
|
||||
})
|
||||
|
||||
listener?.({
|
||||
type,
|
||||
id: "event-1",
|
||||
data: { sessionID: session.id, error: { type: "api", message: "failed", status: 500 } },
|
||||
})
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
expect(dispose.state.session.all(session.id)).toHaveLength(1)
|
||||
expect(alerts).toEqual([])
|
||||
|
||||
tabs.store = [{ type: "session", server, sessionId: session.id }]
|
||||
listener?.({
|
||||
type,
|
||||
id: "event-2",
|
||||
data: { sessionID: session.id, error: { type: "api", message: "failed", status: 500 } },
|
||||
})
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
expect(alerts).toEqual([title])
|
||||
dispose.dispose()
|
||||
})
|
||||
@@ -228,8 +228,7 @@ export function createServerNotificationState(input: {
|
||||
if (!session) return
|
||||
if (session.parentID) return
|
||||
|
||||
const hasOpenTab = sessionIDHasOpenTab(tabs.store, input.key, sessionID)
|
||||
if (hasOpenTab && settings.sounds.agentEnabled()) {
|
||||
if (sessionIDHasOpenTab(tabs.store, input.key, sessionID) && settings.sounds.agentEnabled()) {
|
||||
void input.coordinator.sound(`${input.key}\0${eventID}`, () => playSoundById(settings.sounds.agent()))
|
||||
}
|
||||
|
||||
@@ -241,7 +240,7 @@ export function createServerNotificationState(input: {
|
||||
session: sessionID,
|
||||
})
|
||||
|
||||
if (hasOpenTab && settings.notifications.agent()) {
|
||||
if (settings.notifications.agent()) {
|
||||
void input.coordinator.system(`${input.key}\0${eventID}`, () =>
|
||||
platform.notify(language.t("notification.session.responseReady.title"), session.title ?? sessionID, () =>
|
||||
openNotificationSession(tabs, input.key, sessionID),
|
||||
@@ -256,8 +255,7 @@ export function createServerNotificationState(input: {
|
||||
if (meta.disposed) return
|
||||
if (session?.parentID) return
|
||||
|
||||
const hasOpenTab = sessionIDHasOpenTab(tabs.store, input.key, sessionID)
|
||||
if (hasOpenTab && settings.sounds.errorsEnabled()) {
|
||||
if (sessionIDHasOpenTab(tabs.store, input.key, sessionID) && settings.sounds.errorsEnabled()) {
|
||||
void input.coordinator.sound(`${input.key}\0${eventID}`, () => playSoundById(settings.sounds.errors()))
|
||||
}
|
||||
|
||||
@@ -272,7 +270,7 @@ export function createServerNotificationState(input: {
|
||||
const description =
|
||||
session?.title ??
|
||||
(typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
|
||||
if (hasOpenTab && settings.notifications.errors()) {
|
||||
if (settings.notifications.errors()) {
|
||||
void input.coordinator.system(`${input.key}\0${eventID}`, () =>
|
||||
platform.notify(language.t("notification.session.error.title"), description, () =>
|
||||
openNotificationSession(tabs, input.key, sessionID),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/cli",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -183,26 +183,6 @@ const Root = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME
|
||||
),
|
||||
},
|
||||
}),
|
||||
Spec.make("export", {
|
||||
description: "print stored credentials, including secrets, as JSON",
|
||||
params: {
|
||||
...ServerParams,
|
||||
target: Argument.string("target").pipe(
|
||||
Argument.withDescription("Integration ID or name (exports every integration when omitted)"),
|
||||
Argument.optional,
|
||||
),
|
||||
},
|
||||
}),
|
||||
Spec.make("import", {
|
||||
description: "import credentials exported by auth export",
|
||||
params: {
|
||||
...ServerParams,
|
||||
file: Argument.string("file").pipe(
|
||||
Argument.withDescription("JSON file to import (reads stdin when omitted)"),
|
||||
Argument.optional,
|
||||
),
|
||||
},
|
||||
}),
|
||||
Spec.make("switch", {
|
||||
description: "switch the active account for an integration",
|
||||
params: {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { EOL } from "node:os"
|
||||
import { Effect, Option } from "effect"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { createClient, loadIntegrations, request, resolveIntegration } from "./shared"
|
||||
import { errorMessage } from "../../../util/error"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.auth.commands.export,
|
||||
Effect.fn("cli.auth.export")(
|
||||
function* (input) {
|
||||
const client = yield* createClient({ server: Option.getOrUndefined(input.server), standalone: input.standalone })
|
||||
const target = Option.getOrUndefined(input.target)
|
||||
const integrationID = target ? (yield* resolveIntegration(yield* loadIntegrations(client), target)).id : undefined
|
||||
const credentials = (yield* request((signal) => client.credential.list({ signal }))).filter(
|
||||
(credential) => !integrationID || credential.integrationID === integrationID,
|
||||
)
|
||||
if (process.stdout.isTTY)
|
||||
process.stderr.write(
|
||||
"Warning: the output contains secrets; redirect it to a file or pipe it to auth import" + EOL,
|
||||
)
|
||||
process.stdout.write(JSON.stringify(credentials, null, 2) + EOL)
|
||||
},
|
||||
Effect.catch((error) =>
|
||||
Effect.sync(() => {
|
||||
process.stderr.write(errorMessage(error) + EOL)
|
||||
process.exitCode = 1
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -1,58 +0,0 @@
|
||||
import { EOL } from "node:os"
|
||||
import { isConflictError, type CredentialCreateInput } from "@opencode/client"
|
||||
import { Credential } from "@opencode/schema/credential"
|
||||
import { Effect, Option, Schema } from "effect"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { createClient, request } from "./shared"
|
||||
import { errorMessage } from "../../../util/error"
|
||||
import { readStdin } from "../../../util/io"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.auth.commands.import,
|
||||
Effect.fn("cli.auth.import")(
|
||||
function* (input) {
|
||||
const file = Option.getOrUndefined(input.file)
|
||||
if (!file && process.stdin.isTTY)
|
||||
return yield* Effect.fail(new Error("Pipe auth export output into stdin or pass a file to import"))
|
||||
const text = yield* request(() => (file ? Bun.file(file).text() : readStdin()))
|
||||
const credentials = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Array(Credential.Entry)))(text)
|
||||
const client = yield* createClient({ server: Option.getOrUndefined(input.server), standalone: input.standalone })
|
||||
const existing = yield* request((signal) => client.credential.list({ signal }))
|
||||
const ids = new Set(existing.map((credential) => credential.id))
|
||||
const integrations = new Set(existing.map((credential) => credential.integrationID))
|
||||
const results = yield* Effect.forEach(credentials, (credential) => {
|
||||
if (ids.has(credential.id)) return Effect.succeed(false)
|
||||
return request((signal) =>
|
||||
client.credential.create(
|
||||
{
|
||||
id: credential.id,
|
||||
integrationID: credential.integrationID,
|
||||
label: credential.label,
|
||||
// Stored metadata is JSON, but the credential schema types it as unknown while the generated client expects JSON.
|
||||
value: credential.value as CredentialCreateInput["value"],
|
||||
// Keep the destination's current selections; only integrations new to it adopt the exported selection.
|
||||
activate: credential.active && !integrations.has(credential.integrationID),
|
||||
},
|
||||
{ signal },
|
||||
),
|
||||
).pipe(
|
||||
Effect.as(true),
|
||||
Effect.catchIf(isConflictError, () => Effect.succeed(false)),
|
||||
)
|
||||
})
|
||||
const imported = results.filter(Boolean).length
|
||||
process.stderr.write(
|
||||
`Imported ${imported} ${imported === 1 ? "credential" : "credentials"}` +
|
||||
(results.length > imported ? `, skipped ${results.length - imported} already present` : "") +
|
||||
EOL,
|
||||
)
|
||||
},
|
||||
Effect.catch((error) =>
|
||||
Effect.sync(() => {
|
||||
process.stderr.write(errorMessage(error) + EOL)
|
||||
process.exitCode = 1
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -4,12 +4,12 @@ import { OpenCode } from "@opencode/client"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { ServerConnection } from "../../../services/server-connection"
|
||||
import { ServiceConfig } from "../../../services/service-config"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.debug.commands.agents,
|
||||
Effect.fn("cli.debug.agents")(function* () {
|
||||
const { endpoint } = yield* ServerConnection.resolve()
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const response = yield* Effect.promise(() => client.agent.list({ location: { directory: process.cwd() } }))
|
||||
process.stdout.write(
|
||||
|
||||
@@ -4,13 +4,13 @@ import { OpenCode } from "@opencode/client"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { ServerConnection } from "../../../services/server-connection"
|
||||
import { ServiceConfig } from "../../../services/service-config"
|
||||
import { redactConfig } from "./redact"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.debug.commands.config,
|
||||
Effect.fn("cli.debug.config")(function* () {
|
||||
const { endpoint } = yield* ServerConnection.resolve()
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const entries = yield* Effect.promise(() => client.config.get({ location: { directory: process.cwd() } }))
|
||||
process.stdout.write(JSON.stringify(redactConfig(entries), null, 2) + EOL)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { OpenCode, type IntegrationInfo, type IntegrationOAuthMethod, type McpSe
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { ServerConnection } from "../../../services/server-connection"
|
||||
import { ServiceConfig } from "../../../services/service-config"
|
||||
import { selectIntegration, type IntegrationChoice } from "../../../ui/integration-picker"
|
||||
import { handlePromptErrors, prompt, requireInteractive } from "../../../ui/prompt"
|
||||
import { answerForm } from "../auth/form"
|
||||
@@ -21,7 +21,7 @@ export default Runtime.handler(
|
||||
const authenticate = Effect.fn("cli.mcp.auth.run")(function* (name?: string) {
|
||||
if (!name) yield* requireInteractive("Pass an MCP server name when running without an interactive terminal")
|
||||
intro("Authenticate an MCP server")
|
||||
const { endpoint } = yield* ServerConnection.resolve()
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const integrations = yield* loadIntegrations(client)
|
||||
const servers = yield* request((signal) => client.mcp.list({ location }, { signal }))
|
||||
|
||||
@@ -4,12 +4,12 @@ import { OpenCode, type McpServer } from "@opencode/client"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { ServerConnection } from "../../../services/server-connection"
|
||||
import { ServiceConfig } from "../../../services/service-config"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.mcp.commands.list,
|
||||
Effect.fn("cli.mcp.list")(function* () {
|
||||
const { endpoint } = yield* ServerConnection.resolve()
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const response = yield* Effect.promise(() => client.mcp.list({ location: { directory: process.cwd() } }))
|
||||
const servers = response.data.toSorted((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
@@ -4,7 +4,7 @@ import { OpenCode } from "@opencode/client"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { ServerConnection } from "../../../services/server-connection"
|
||||
import { ServiceConfig } from "../../../services/service-config"
|
||||
import { resolveIntegration } from "./resolve"
|
||||
|
||||
const location = { directory: process.cwd() }
|
||||
@@ -12,7 +12,7 @@ const location = { directory: process.cwd() }
|
||||
export default Runtime.handler(
|
||||
Commands.commands.mcp.commands.logout,
|
||||
Effect.fn("cli.mcp.logout")(function* (input) {
|
||||
const { endpoint } = yield* ServerConnection.resolve()
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
|
||||
const integration = yield* resolveIntegration(client, input.name, location)
|
||||
|
||||
@@ -10,10 +10,6 @@ import { ServiceConfig } from "../../services/service-config"
|
||||
export default Runtime.handler(
|
||||
Commands.commands.pair,
|
||||
Effect.fn("cli.pair")(function* (input: Runtime.Input<typeof Commands.commands.pair>) {
|
||||
if ((yield* ServiceConfig.read()).disabled === true)
|
||||
return yield* Effect.fail(
|
||||
new Error("Pairing requires the background service; run `opencode service unset disabled` first"),
|
||||
)
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const urls = Option.isSome(input.url)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { OpenCode, type PluginInfo } from "@opencode/client"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { Npm } from "@opencode/util/npm"
|
||||
import { Config } from "../../../config"
|
||||
import { ServerConnection } from "../../../services/server-connection"
|
||||
import { ServiceConfig } from "../../../services/service-config"
|
||||
|
||||
export interface Item {
|
||||
readonly runtime: "Server" | "TUI"
|
||||
@@ -16,7 +16,7 @@ export interface Item {
|
||||
}
|
||||
|
||||
export const inspect = Effect.fn("cli.plugin.inspect")(function* (selected?: string) {
|
||||
const { endpoint } = yield* ServerConnection.resolve()
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const location = { directory: process.cwd() }
|
||||
const listed = yield* Effect.promise(() => client.plugin.list({ location }))
|
||||
|
||||
@@ -5,7 +5,7 @@ import { OpenCode, type PluginInfo } from "@opencode/client"
|
||||
import { Service } from "@opencode/client/effect/service"
|
||||
import { Commands } from "../../commands"
|
||||
import { Runtime } from "../../../framework/runtime"
|
||||
import { ServerConnection } from "../../../services/server-connection"
|
||||
import { ServiceConfig } from "../../../services/service-config"
|
||||
import { Config } from "../../../config"
|
||||
import { Global } from "@opencode/util/global"
|
||||
import { Npm } from "@opencode/util/npm"
|
||||
@@ -16,7 +16,7 @@ import { discoverPluginTargets, localPluginDirectories, localSource } from "@ope
|
||||
export default Runtime.handler(
|
||||
Commands.commands.plugin.commands.list,
|
||||
Effect.fn("cli.plugin.list")(function* (input) {
|
||||
const { endpoint } = yield* ServerConnection.resolve()
|
||||
const endpoint = yield* Service.ensure(yield* ServiceConfig.options())
|
||||
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) })
|
||||
const response = yield* Effect.promise(() => client.plugin.list({ location: { directory: process.cwd() } }))
|
||||
const config = yield* Config.Service
|
||||
|
||||
@@ -31,8 +31,6 @@ const Handlers = Runtime.handlers(Commands, {
|
||||
list: () => import("./commands/handlers/auth/list"),
|
||||
login: () => import("./commands/handlers/auth/login"),
|
||||
logout: () => import("./commands/handlers/auth/logout"),
|
||||
export: () => import("./commands/handlers/auth/export"),
|
||||
import: () => import("./commands/handlers/auth/import"),
|
||||
switch: () => import("./commands/handlers/auth/switch"),
|
||||
},
|
||||
debug: {
|
||||
|
||||
@@ -67,9 +67,6 @@ type FormRequest = Extract<V2Event, { type: "form.created" }>["data"]["form"]
|
||||
// attached client must not cancel input that may belong to another session.
|
||||
const GLOBAL_FORM_SESSION_ID = "global"
|
||||
|
||||
const PERMISSION_REJECTED_FEEDBACK =
|
||||
"This non-interactive run cannot ask the user for permission, so the request was rejected. Continue without this action."
|
||||
|
||||
export async function runNonInteractivePrompt(input: Input) {
|
||||
const controller = new AbortController()
|
||||
const stream = input.client.event.subscribe({ signal: controller.signal })[Symbol.asyncIterator]()
|
||||
@@ -135,34 +132,10 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
}
|
||||
}
|
||||
|
||||
// Subagents run in child sessions; their asks and questions belong to this run too. Other
|
||||
// sessions on a shared server (e.g. the TUI's) must be left alone.
|
||||
const owned = new Map<string, Promise<boolean>>([[input.sessionID, Promise.resolve(true)]])
|
||||
const ownsSession = (sessionID: string): Promise<boolean> => {
|
||||
const known = owned.get(sessionID)
|
||||
if (known) return known
|
||||
const result =
|
||||
sessionID === GLOBAL_FORM_SESSION_ID
|
||||
? Promise.resolve(false)
|
||||
: input.client.session
|
||||
.get({ sessionID })
|
||||
.then((session) => (session.parentID ? ownsSession(session.parentID) : false))
|
||||
.catch(() => false)
|
||||
owned.set(sessionID, result)
|
||||
return result
|
||||
}
|
||||
|
||||
const replyPermission = async (request: {
|
||||
id: string
|
||||
sessionID: string
|
||||
action: string
|
||||
resources: ReadonlyArray<string>
|
||||
}) => {
|
||||
// Nobody can approve here. Outside V1 compatibility, reject with feedback so the tool fails
|
||||
// as ordinary model-visible output and the model continues without the action.
|
||||
const continuing = !input.auto && input.compatibility !== "v1"
|
||||
const replyPermission = async (request: { id: string; action: string; resources: ReadonlyArray<string> }) => {
|
||||
if (!input.auto) {
|
||||
if (!continuing) permissionRejected = true
|
||||
permissionRejected = true
|
||||
if (input.compatibility !== "v1") process.exitCode = 1
|
||||
UI.println(
|
||||
UI.Style.TEXT_WARNING_BOLD + "!",
|
||||
UI.Style.TEXT_NORMAL +
|
||||
@@ -171,13 +144,12 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
}
|
||||
await input.client.permission
|
||||
.reply({
|
||||
sessionID: request.sessionID,
|
||||
sessionID: input.sessionID,
|
||||
requestID: request.id,
|
||||
decision: input.auto ? "once" : "reject",
|
||||
...(continuing ? { message: PERMISSION_REJECTED_FEEDBACK } : {}),
|
||||
})
|
||||
.catch(() => {})
|
||||
if (!input.auto && !continuing) {
|
||||
if (!input.auto) {
|
||||
await input.client.session.interrupt({ sessionID: input.sessionID }).catch(() => {})
|
||||
}
|
||||
}
|
||||
@@ -207,14 +179,14 @@ export async function runNonInteractivePrompt(input: Input) {
|
||||
}
|
||||
const event = next.value
|
||||
|
||||
if (event.type === "permission.asked" && submitted && (await ownsSession(event.data.sessionID))) {
|
||||
if (event.type === "permission.asked" && submitted && event.data.sessionID === input.sessionID) {
|
||||
await replyPermission(event.data)
|
||||
continue
|
||||
}
|
||||
if (
|
||||
event.type === "form.created" &&
|
||||
submitted &&
|
||||
((await ownsSession(event.data.form.sessionID)) ||
|
||||
(event.data.form.sessionID === input.sessionID ||
|
||||
(!input.attached &&
|
||||
event.data.form.sessionID === GLOBAL_FORM_SESSION_ID &&
|
||||
sameLocation(event.location, input.location)))
|
||||
|
||||
@@ -18,7 +18,7 @@ export type Resolved = {
|
||||
readonly service?: ReturnType<typeof managedService>
|
||||
}
|
||||
|
||||
export const resolve = Effect.fn("cli.server-connection.resolve")(function* (args: Args = {}) {
|
||||
export const resolve = Effect.fn("cli.server-connection.resolve")(function* (args: Args) {
|
||||
if (args.server !== undefined && args.standalone)
|
||||
return yield* Effect.fail(new Error("--server and --standalone cannot be combined"))
|
||||
if (args.server !== undefined) {
|
||||
@@ -38,7 +38,7 @@ export const resolve = Effect.fn("cli.server-connection.resolve")(function* (arg
|
||||
)
|
||||
return { endpoint } satisfies Resolved
|
||||
}
|
||||
if (args.standalone || (yield* ServiceConfig.read()).disabled === true) {
|
||||
if (args.standalone) {
|
||||
return { endpoint: yield* Standalone.start() } satisfies Resolved
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { selfCommand } from "../util/process"
|
||||
// registration file (by channel), which version, and how to spawn opencode.
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
disabled: Schema.optional(Schema.Boolean),
|
||||
hostname: Schema.optional(Schema.String),
|
||||
port: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(65_535))),
|
||||
password: Schema.optional(Schema.String),
|
||||
@@ -21,7 +20,7 @@ export const Info = Schema.Struct({
|
||||
})
|
||||
export type Info = typeof Info.Type
|
||||
|
||||
const keys = ["disabled", "hostname", "port", "password", "cors", "env"] as const
|
||||
const keys = ["hostname", "port", "password", "cors", "env"] as const
|
||||
type Key = (typeof keys)[number]
|
||||
|
||||
const decodeInfo = Schema.decodeUnknownEffect(Schema.fromJsonString(Info))
|
||||
@@ -79,15 +78,7 @@ export const migrateConfig = Effect.fnUntraced(function* (legacy: string, file:
|
||||
})
|
||||
|
||||
function configKey(key: string): Key {
|
||||
if (
|
||||
key === "disabled" ||
|
||||
key === "hostname" ||
|
||||
key === "port" ||
|
||||
key === "password" ||
|
||||
key === "cors" ||
|
||||
key === "env"
|
||||
)
|
||||
return key
|
||||
if (key === "hostname" || key === "port" || key === "password" || key === "cors" || key === "env") return key
|
||||
throw new Error(`Unknown service config key: ${key}`)
|
||||
}
|
||||
|
||||
@@ -160,9 +151,6 @@ export const get = Effect.fn("cli.service-config.get")(function* (key?: string,
|
||||
const selected = configKey(key)
|
||||
if (selected !== "env" && name !== undefined) throw new Error(`Usage: opencode service get ${selected}`)
|
||||
switch (selected) {
|
||||
case "disabled": {
|
||||
return String((yield* read()).disabled ?? false)
|
||||
}
|
||||
case "hostname": {
|
||||
return (yield* read()).hostname ?? ""
|
||||
}
|
||||
@@ -189,12 +177,6 @@ export const set = Effect.fn("cli.service-config.set")(function* (key: string, v
|
||||
if (selected !== "env" && nestedValue !== undefined)
|
||||
throw new Error(`Usage: opencode service set ${selected} <value>`)
|
||||
switch (selected) {
|
||||
case "disabled": {
|
||||
if (value !== "true" && value !== "false") throw new Error("Disabled must be true or false")
|
||||
if (value === "true") yield* Service.stop(yield* options())
|
||||
yield* write({ ...(yield* read()), disabled: value === "true" })
|
||||
return
|
||||
}
|
||||
case "hostname": {
|
||||
yield* Service.stop(yield* options())
|
||||
yield* write({ ...(yield* read()), hostname: value })
|
||||
@@ -239,11 +221,6 @@ export const unset = Effect.fn("cli.service-config.unset")(function* (key: strin
|
||||
const selected = configKey(key)
|
||||
if (selected !== "env" && name !== undefined) throw new Error(`Usage: opencode service unset ${selected}`)
|
||||
switch (selected) {
|
||||
case "disabled": {
|
||||
const { disabled: _disabled, ...next } = yield* read()
|
||||
yield* write(next)
|
||||
return
|
||||
}
|
||||
case "hostname": {
|
||||
yield* Service.stop(yield* options())
|
||||
const { hostname: _hostname, ...next } = yield* read()
|
||||
|
||||
@@ -38,7 +38,7 @@ test("resolution groups Effect-native lifecycle operations only for the managed
|
||||
pid: process.pid,
|
||||
}),
|
||||
)
|
||||
const resolved = await runPromise(ServerConnection.resolve())
|
||||
const resolved = await runPromise(ServerConnection.resolve({}))
|
||||
|
||||
expect(resolved.endpoint.url).toBe(server.url.toString())
|
||||
expect(resolved.service).toBeDefined()
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Service, type Info } from "@opencode/client/effect/service"
|
||||
import { Global } from "@opencode/util/global"
|
||||
import { OPENCODE_VERSION } from "../src/version"
|
||||
import { expect, test } from "bun:test"
|
||||
import { Effect, FileSystem, Schema } from "effect"
|
||||
import { Effect, Schema } from "effect"
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
import path from "node:path"
|
||||
@@ -21,24 +21,6 @@ test("managed service ports are stable per installation channel", () => {
|
||||
expect(ServiceConfig.defaultPort("preview-a")).not.toBe(ServiceConfig.defaultPort("preview-b"))
|
||||
})
|
||||
|
||||
test("service disabled accepts only booleans without changing configuration on invalid input", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-service-disabled-config-"))
|
||||
const layer = Global.layerWith({ config: path.join(root, "config"), state: path.join(root, "state") })
|
||||
const run = <A, E>(effect: Effect.Effect<A, E, Global.Service | FileSystem.FileSystem>) =>
|
||||
Effect.runPromise(effect.pipe(Effect.provide(layer), Effect.provide(NodeFileSystem.layer)))
|
||||
try {
|
||||
expect(await run(ServiceConfig.get("disabled"))).toBe("false")
|
||||
await expect(run(ServiceConfig.set("disabled", "yes"))).rejects.toThrow("Disabled must be true or false")
|
||||
expect(await run(ServiceConfig.read())).toEqual({})
|
||||
await run(ServiceConfig.set("disabled", "true"))
|
||||
expect(await run(ServiceConfig.read())).toEqual({ disabled: true })
|
||||
await run(ServiceConfig.unset("disabled"))
|
||||
expect(await run(ServiceConfig.read())).toEqual({})
|
||||
} finally {
|
||||
await fs.rm(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test("local channel stores service config with the local service filename", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-service-"))
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/client",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
|
||||
@@ -710,12 +710,7 @@ export type SessionLogOutput =
|
||||
| undefined
|
||||
readonly data: {
|
||||
readonly sessionID: Session.ID
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -907,12 +902,7 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: Session.ID
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly finish?: "content-filter" | undefined
|
||||
readonly rawFinish?: string | undefined
|
||||
readonly providerState?: SessionMessage.ProviderState | undefined
|
||||
@@ -1122,12 +1112,7 @@ export type SessionLogOutput =
|
||||
readonly sessionID: Session.ID
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly id: string
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly content?:
|
||||
| readonly [
|
||||
(
|
||||
@@ -1172,12 +1157,7 @@ export type SessionLogOutput =
|
||||
readonly assistantMessageID: SessionMessage.ID
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
}
|
||||
}
|
||||
| {
|
||||
@@ -1258,12 +1238,7 @@ export type SessionLogOutput =
|
||||
readonly data: {
|
||||
readonly sessionID: Session.ID
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number | undefined
|
||||
readonly response?: { readonly body: string } | undefined
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number | undefined }
|
||||
readonly inputID?: SessionMessage.ID | undefined
|
||||
readonly cost?: (number & Brand.Brand<"Money.USD">) | undefined
|
||||
readonly tokens?:
|
||||
@@ -1743,21 +1718,6 @@ export interface McpApi<E = never> {
|
||||
readonly resource: { readonly catalog: McpResourceCatalogOperation<E> }
|
||||
}
|
||||
|
||||
export type CredentialListOutput = ReadonlyArray<Credential.Entry>
|
||||
export type CredentialListOperation<E = never> = () => Effect.Effect<CredentialListOutput, E>
|
||||
|
||||
export type CredentialCreateInput = {
|
||||
readonly id?: Credential.ID | undefined
|
||||
readonly integrationID: Integration.ID
|
||||
readonly label?: string | undefined
|
||||
readonly value: Credential.Value
|
||||
readonly activate?: boolean | undefined
|
||||
}
|
||||
export type CredentialCreateOutput = Credential.Entry
|
||||
export type CredentialCreateOperation<E = never> = (
|
||||
input: CredentialCreateInput,
|
||||
) => Effect.Effect<CredentialCreateOutput, E>
|
||||
|
||||
export type CredentialUpdateInput = { readonly credentialID: Credential.ID; readonly label: string }
|
||||
export type CredentialUpdateOutput = void
|
||||
export type CredentialUpdateOperation<E = never> = (
|
||||
@@ -1777,8 +1737,6 @@ export type CredentialRemoveOperation<E = never> = (
|
||||
) => Effect.Effect<CredentialRemoveOutput, E>
|
||||
|
||||
export interface CredentialApi<E = never> {
|
||||
readonly list: CredentialListOperation<E>
|
||||
readonly create: CredentialCreateOperation<E>
|
||||
readonly update: CredentialUpdateOperation<E>
|
||||
readonly activate: CredentialActivateOperation<E>
|
||||
readonly remove: CredentialRemoveOperation<E>
|
||||
|
||||
@@ -153,9 +153,6 @@ import type {
|
||||
McpDisconnectOutput,
|
||||
McpResourceCatalogInput,
|
||||
McpResourceCatalogOutput,
|
||||
CredentialListOutput,
|
||||
CredentialCreateInput,
|
||||
CredentialCreateOutput,
|
||||
CredentialUpdateInput,
|
||||
CredentialUpdateOutput,
|
||||
CredentialActivateInput,
|
||||
@@ -1024,30 +1021,6 @@ const adaptGroupMcp = (raw: RawClient["server.mcp"]) => ({
|
||||
resource: { catalog: EndpointMcpResourceCatalog(raw) },
|
||||
})
|
||||
|
||||
const EndpointCredentialList = (raw: RawClient["server.credential"]) => () =>
|
||||
preserveEffect<CredentialListOutput>()(
|
||||
raw["credential.list"]({}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const EndpointCredentialCreate = (raw: RawClient["server.credential"]) => (input: CredentialCreateInput) =>
|
||||
preserveEffect<CredentialCreateOutput>()(
|
||||
raw["credential.create"]({
|
||||
payload: {
|
||||
id: input["id"],
|
||||
integrationID: input["integrationID"],
|
||||
label: input["label"],
|
||||
value: input["value"],
|
||||
activate: input["activate"],
|
||||
},
|
||||
}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const EndpointCredentialUpdate = (raw: RawClient["server.credential"]) => (input: CredentialUpdateInput) =>
|
||||
preserveEffect<CredentialUpdateOutput>()(
|
||||
raw["credential.update"]({
|
||||
@@ -1069,8 +1042,6 @@ const EndpointCredentialRemove = (raw: RawClient["server.credential"]) => (input
|
||||
)
|
||||
|
||||
const adaptGroupCredential = (raw: RawClient["server.credential"]) => ({
|
||||
list: EndpointCredentialList(raw),
|
||||
create: EndpointCredentialCreate(raw),
|
||||
update: EndpointCredentialUpdate(raw),
|
||||
activate: EndpointCredentialActivate(raw),
|
||||
remove: EndpointCredentialRemove(raw),
|
||||
|
||||
@@ -147,9 +147,6 @@ import type {
|
||||
McpDisconnectOutput,
|
||||
McpResourceCatalogInput,
|
||||
McpResourceCatalogOutput,
|
||||
CredentialListOutput,
|
||||
CredentialCreateInput,
|
||||
CredentialCreateOutput,
|
||||
CredentialUpdateInput,
|
||||
CredentialUpdateOutput,
|
||||
CredentialActivateInput,
|
||||
@@ -1395,29 +1392,6 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
},
|
||||
credential: {
|
||||
list: (requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: CredentialListOutput }>(
|
||||
{ method: "GET", path: `/api/credential`, successStatus: 200, declaredStatuses: [400, 401], empty: false },
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
create: (input: CredentialCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: CredentialCreateOutput }>(
|
||||
{
|
||||
method: "POST",
|
||||
path: `/api/credential`,
|
||||
body: {
|
||||
id: input["id"],
|
||||
integrationID: input["integrationID"],
|
||||
label: input["label"],
|
||||
value: input["value"],
|
||||
activate: input["activate"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401, 409],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
update: (input: CredentialUpdateInput, requestOptions?: RequestOptions) =>
|
||||
request<CredentialUpdateOutput>(
|
||||
{
|
||||
|
||||
@@ -133,7 +133,7 @@ export type ToolTextContent = { type: "text"; text: string }
|
||||
|
||||
export type ToolFileContent = { type: "file"; uri: string; mime: string; name?: string | null }
|
||||
|
||||
export type SessionStructuredError = { type: string; message: string; status?: number; response?: { body: string } }
|
||||
export type SessionStructuredError = { type: string; message: string; status?: number }
|
||||
|
||||
export type SessionMessageCompactionRunning = {
|
||||
type: "compaction"
|
||||
@@ -235,7 +235,9 @@ export type IntegrationCommandMethod = { id: string; type: "command"; label: str
|
||||
|
||||
export type IntegrationEnvMethod = { type: "env"; names: Array<string> }
|
||||
|
||||
export type ConnectionStatus = { status: "needs_auth"; message: string; url?: string }
|
||||
export type ConnectionCredentialInfo = { type: "credential"; id: string; label: string; method: "key" | "oauth" }
|
||||
|
||||
export type ConnectionEnvInfo = { type: "env"; name: string }
|
||||
|
||||
export type IntegrationAttempt = {
|
||||
attemptID: string
|
||||
@@ -293,15 +295,6 @@ export type McpResourceTemplate = {
|
||||
mimeType?: string
|
||||
}
|
||||
|
||||
export type CredentialOAuth = {
|
||||
type: "oauth"
|
||||
methodID: string
|
||||
refresh: string
|
||||
access: string
|
||||
expires: number
|
||||
metadata?: { [x: string]: JsonValue }
|
||||
}
|
||||
|
||||
export type ProjectVcs = string
|
||||
|
||||
export type ProjectIcon = { url?: string; override?: string; color?: string }
|
||||
@@ -1476,15 +1469,7 @@ export type ModelCost = {
|
||||
cache: { read: MoneyUSDPerMillionTokens; write: MoneyUSDPerMillionTokens }
|
||||
}
|
||||
|
||||
export type ConnectionCredentialInfo = {
|
||||
type: "credential"
|
||||
id: string
|
||||
label: string
|
||||
method: "key" | "oauth"
|
||||
status?: ConnectionStatus
|
||||
}
|
||||
|
||||
export type ConnectionEnvInfo = { type: "env"; name: string; status?: ConnectionStatus }
|
||||
export type ConnectionInfo = ConnectionCredentialInfo | ConnectionEnvInfo
|
||||
|
||||
export type McpServer = {
|
||||
name: string
|
||||
@@ -1665,7 +1650,7 @@ export type FormMultiselectField1 = {
|
||||
default?: Array<string>
|
||||
}
|
||||
|
||||
export type FormAnswer2 = { [x: string]: FormValue1 }
|
||||
export type FormAnswer1 = { [x: string]: FormValue1 }
|
||||
|
||||
export type SessionStatusUpdated = {
|
||||
id: string
|
||||
@@ -1899,15 +1884,6 @@ export type FormField =
|
||||
|
||||
export type FormState = { status: "pending" } | { status: "answered"; answer: FormAnswer } | { status: "cancelled" }
|
||||
|
||||
export type CredentialKey = {
|
||||
type: "key"
|
||||
key: string
|
||||
metadata?: { [x: string]: JsonValue }
|
||||
configuration?: FormAnswer
|
||||
}
|
||||
|
||||
export type ConnectionInfo = ConnectionCredentialInfo | ConnectionEnvInfo
|
||||
|
||||
export type FormField1 =
|
||||
| FormStringField1
|
||||
| FormNumberField1
|
||||
@@ -1922,7 +1898,7 @@ export type FormReplied = {
|
||||
metadata?: { [x: string]: any }
|
||||
type: "form.replied"
|
||||
location?: LocationRef
|
||||
data: { id: string; sessionID: string; answer: FormAnswer2 }
|
||||
data: { id: string; sessionID: string; answer: FormAnswer1 }
|
||||
}
|
||||
|
||||
export type ReferenceInfo = {
|
||||
@@ -2241,8 +2217,6 @@ export type SessionMessageAssistantTool1 = {
|
||||
|
||||
export type FormFields = [FormField, ...Array<FormField>]
|
||||
|
||||
export type CredentialValue = CredentialOAuth | CredentialKey
|
||||
|
||||
export type FormFields2 = [FormField1, ...Array<FormField1>]
|
||||
|
||||
export type SessionsResponse = { data: Array<SessionInfo>; cursor: { previous?: string | null; next?: string | null } }
|
||||
@@ -2297,14 +2271,6 @@ export type IntegrationOAuthMethod = { id: string; type: "oauth"; label: string;
|
||||
|
||||
export type IntegrationKeyMethod = { type: "key"; label?: string; form?: FormFields }
|
||||
|
||||
export type CredentialEntry = {
|
||||
id: string
|
||||
integrationID: string
|
||||
label: string
|
||||
active: boolean
|
||||
value: CredentialValue
|
||||
}
|
||||
|
||||
export type FormInfo1 = { id: string; sessionID: string; title: string; metadata?: FormMetadata1; fields: FormFields2 }
|
||||
|
||||
export type SessionMessageInfo =
|
||||
@@ -3209,12 +3175,7 @@ export type SessionImportInput = {
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly content?: readonly [
|
||||
(
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
@@ -3251,21 +3212,11 @@ export type SessionImportInput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error?: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
}
|
||||
}
|
||||
| (
|
||||
@@ -3317,12 +3268,7 @@ export type SessionImportInput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly status: "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3546,12 +3492,7 @@ export type SessionImportInput = {
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly content?: readonly [
|
||||
(
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
@@ -3588,21 +3529,11 @@ export type SessionImportInput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error?: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
}
|
||||
}
|
||||
| (
|
||||
@@ -3654,12 +3585,7 @@ export type SessionImportInput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly status: "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -3883,12 +3809,7 @@ export type SessionImportInput = {
|
||||
| {
|
||||
readonly status: "error"
|
||||
readonly input: { readonly [x: string]: JsonValue }
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly content?: readonly [
|
||||
(
|
||||
| { readonly type: "text"; readonly text: string }
|
||||
@@ -3925,21 +3846,11 @@ export type SessionImportInput = {
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly error?: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error?: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly retry?: {
|
||||
readonly attempt: number
|
||||
readonly at: number
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
}
|
||||
}
|
||||
| (
|
||||
@@ -3991,12 +3902,7 @@ export type SessionImportInput = {
|
||||
readonly time: { readonly created: number }
|
||||
readonly status: "failed"
|
||||
readonly reason: "auto" | "manual"
|
||||
readonly error: {
|
||||
readonly type: string
|
||||
readonly message: string
|
||||
readonly status?: number
|
||||
readonly response?: { readonly body: string }
|
||||
}
|
||||
readonly error: { readonly type: string; readonly message: string; readonly status?: number }
|
||||
readonly cost?: number
|
||||
readonly tokens?: {
|
||||
readonly input: number
|
||||
@@ -5776,128 +5682,6 @@ export type McpResourceCatalogInput = {
|
||||
|
||||
export type McpResourceCatalogOutput = { location: LocationPublicRef; data: McpResourceCatalog }
|
||||
|
||||
export type CredentialListOutput = { data: Array<CredentialEntry> }["data"]
|
||||
|
||||
export type CredentialCreateInput = {
|
||||
readonly id?: {
|
||||
readonly id?: string
|
||||
readonly integrationID: string
|
||||
readonly label?: string
|
||||
readonly value:
|
||||
| {
|
||||
readonly type: "oauth"
|
||||
readonly methodID: string
|
||||
readonly refresh: string
|
||||
readonly access: string
|
||||
readonly expires: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly type: "key"
|
||||
readonly key: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly configuration?: {
|
||||
readonly [x: string]: string | number | "Infinity" | "-Infinity" | "NaN" | boolean | ReadonlyArray<string>
|
||||
}
|
||||
}
|
||||
readonly activate?: boolean
|
||||
}["id"]
|
||||
readonly integrationID: {
|
||||
readonly id?: string
|
||||
readonly integrationID: string
|
||||
readonly label?: string
|
||||
readonly value:
|
||||
| {
|
||||
readonly type: "oauth"
|
||||
readonly methodID: string
|
||||
readonly refresh: string
|
||||
readonly access: string
|
||||
readonly expires: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly type: "key"
|
||||
readonly key: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly configuration?: {
|
||||
readonly [x: string]: string | number | "Infinity" | "-Infinity" | "NaN" | boolean | ReadonlyArray<string>
|
||||
}
|
||||
}
|
||||
readonly activate?: boolean
|
||||
}["integrationID"]
|
||||
readonly label?: {
|
||||
readonly id?: string
|
||||
readonly integrationID: string
|
||||
readonly label?: string
|
||||
readonly value:
|
||||
| {
|
||||
readonly type: "oauth"
|
||||
readonly methodID: string
|
||||
readonly refresh: string
|
||||
readonly access: string
|
||||
readonly expires: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly type: "key"
|
||||
readonly key: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly configuration?: {
|
||||
readonly [x: string]: string | number | "Infinity" | "-Infinity" | "NaN" | boolean | ReadonlyArray<string>
|
||||
}
|
||||
}
|
||||
readonly activate?: boolean
|
||||
}["label"]
|
||||
readonly value: {
|
||||
readonly id?: string
|
||||
readonly integrationID: string
|
||||
readonly label?: string
|
||||
readonly value:
|
||||
| {
|
||||
readonly type: "oauth"
|
||||
readonly methodID: string
|
||||
readonly refresh: string
|
||||
readonly access: string
|
||||
readonly expires: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly type: "key"
|
||||
readonly key: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly configuration?: {
|
||||
readonly [x: string]: string | number | "Infinity" | "-Infinity" | "NaN" | boolean | ReadonlyArray<string>
|
||||
}
|
||||
}
|
||||
readonly activate?: boolean
|
||||
}["value"]
|
||||
readonly activate?: {
|
||||
readonly id?: string
|
||||
readonly integrationID: string
|
||||
readonly label?: string
|
||||
readonly value:
|
||||
| {
|
||||
readonly type: "oauth"
|
||||
readonly methodID: string
|
||||
readonly refresh: string
|
||||
readonly access: string
|
||||
readonly expires: number
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
}
|
||||
| {
|
||||
readonly type: "key"
|
||||
readonly key: string
|
||||
readonly metadata?: { readonly [x: string]: JsonValue }
|
||||
readonly configuration?: {
|
||||
readonly [x: string]: string | number | "Infinity" | "-Infinity" | "NaN" | boolean | ReadonlyArray<string>
|
||||
}
|
||||
}
|
||||
readonly activate?: boolean
|
||||
}["activate"]
|
||||
}
|
||||
|
||||
export type CredentialCreateOutput = { data: CredentialEntry }["data"]
|
||||
|
||||
export type CredentialUpdateInput = {
|
||||
readonly credentialID: { readonly credentialID: string }["credentialID"]
|
||||
readonly label: { readonly label: string }["label"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/codemode",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-app",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode/console-core",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-function",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-mail",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"dependencies": {
|
||||
"@jsx-email/all": "2.2.3",
|
||||
"@jsx-email/cli": "1.4.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode/console-support",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "2.0.20",
|
||||
"version": "2.0.19",
|
||||
"name": "@opencode/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -129,7 +129,6 @@
|
||||
"https-proxy-agent": "7.0.6",
|
||||
"ignore": "7.0.5",
|
||||
"immer": "11.1.4",
|
||||
"jose": "6.0.11",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"mime-types": "3.0.2",
|
||||
"tree-sitter-bash": "0.25.0",
|
||||
|
||||
@@ -38,16 +38,11 @@ export interface Interface {
|
||||
readonly list: (integrationID: Integration.ID) => Effect.Effect<Info[]>
|
||||
/** Returns one stored credential by ID. */
|
||||
readonly get: (id: ID) => Effect.Effect<Info | undefined>
|
||||
/**
|
||||
* Creates a credential for an integration and returns the new record. The new credential becomes the
|
||||
* integration's selection unless `activate` is false and the integration already has a credential.
|
||||
*/
|
||||
/** Creates a credential for an integration and returns the new record. */
|
||||
readonly create: (input: {
|
||||
readonly id?: ID
|
||||
readonly integrationID: Integration.ID
|
||||
readonly value: Value
|
||||
readonly label?: string
|
||||
readonly activate?: boolean
|
||||
}) => Effect.Effect<Info>
|
||||
/** Selects a stored credential for its integration. */
|
||||
readonly activate: (id: ID) => Effect.Effect<void>
|
||||
@@ -104,30 +99,19 @@ const layer = Layer.effect(
|
||||
}),
|
||||
create: Effect.fn("Credential.create")(function* (input) {
|
||||
const credential = new Info({
|
||||
id: input.id ?? ID.create(),
|
||||
id: ID.create(),
|
||||
integrationID: input.integrationID,
|
||||
label: input.label ?? "default",
|
||||
value: input.value,
|
||||
})
|
||||
const activated = yield* db
|
||||
yield* db
|
||||
.transaction((tx) =>
|
||||
Effect.gen(function* () {
|
||||
const current = yield* tx
|
||||
.select({ id: CredentialTable.id, active: CredentialTable.active })
|
||||
.from(CredentialTable)
|
||||
yield* tx
|
||||
.update(CredentialTable)
|
||||
.set({ active: false })
|
||||
.where(eq(CredentialTable.integration_id, credential.integrationID))
|
||||
.orderBy(desc(CredentialTable.active), desc(CredentialTable.time_created), desc(CredentialTable.id))
|
||||
.get()
|
||||
const activate = input.activate !== false || !current
|
||||
if (activate)
|
||||
yield* tx
|
||||
.update(CredentialTable)
|
||||
.set({ active: false })
|
||||
.where(eq(CredentialTable.integration_id, credential.integrationID))
|
||||
.run()
|
||||
// Legacy rows have no active flag and the newest one is selected, so pin it before inserting a newer row.
|
||||
if (!activate && current && !current.active)
|
||||
yield* tx.update(CredentialTable).set({ active: true }).where(eq(CredentialTable.id, current.id)).run()
|
||||
.run()
|
||||
yield* tx
|
||||
.insert(CredentialTable)
|
||||
.values({
|
||||
@@ -135,10 +119,9 @@ const layer = Layer.effect(
|
||||
integration_id: credential.integrationID,
|
||||
label: credential.label,
|
||||
value: credential.value,
|
||||
active: activate,
|
||||
active: true,
|
||||
})
|
||||
.run()
|
||||
return activate
|
||||
}),
|
||||
)
|
||||
.pipe(
|
||||
@@ -155,15 +138,13 @@ const layer = Layer.effect(
|
||||
credentialID: credential.id,
|
||||
integrationID: credential.integrationID,
|
||||
type: credential.value.type,
|
||||
active: activated,
|
||||
})
|
||||
yield* bus.publish(Event.Updated, {}, { global: true })
|
||||
if (activated)
|
||||
yield* bus.publish(
|
||||
Event.Switched,
|
||||
{ integrationID: credential.integrationID, credentialID: credential.id },
|
||||
{ global: true },
|
||||
)
|
||||
yield* bus.publish(
|
||||
Event.Switched,
|
||||
{ integrationID: credential.integrationID, credentialID: credential.id },
|
||||
{ global: true },
|
||||
)
|
||||
return credential
|
||||
}),
|
||||
activate: Effect.fn("Credential.activate")(function* (id) {
|
||||
|
||||
@@ -5,8 +5,6 @@ import { sqliteLayer, supportsForeignKeyToggle, supportsTuningPragmas } from "#s
|
||||
import { Context, Effect, Layer, Schema, Semaphore } from "effect"
|
||||
import type { SqlClient } from "effect/unstable/sql"
|
||||
import { Global } from "@opencode/util/global"
|
||||
import { closeSync, existsSync, openSync } from "node:fs"
|
||||
import { chmod } from "node:fs/promises"
|
||||
import { isAbsolute, join } from "path"
|
||||
import { DatabaseMigration } from "./migration.js"
|
||||
import { makeGlobalNode } from "@opencode/util/effect/app-node"
|
||||
@@ -72,31 +70,13 @@ export function layer(options: Options = { path: ":memory:" }) {
|
||||
Layer.provide(sqliteLayer({ filename })),
|
||||
)
|
||||
const filename = options.path ?? ":memory:"
|
||||
if (filename === ":memory:") return provide(filename)
|
||||
const file = isAbsolute(filename) ? filename : join((yield* Global.Service).data, filename)
|
||||
yield* Effect.promise(() => restrictToOwner(file))
|
||||
return provide(file)
|
||||
if (filename === ":memory:" || isAbsolute(filename)) return provide(filename)
|
||||
const global = yield* Global.Service
|
||||
return provide(join(global.data, filename))
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// SQLite creates new sidecars with the database's mode, but does not tighten existing sidecars.
|
||||
// Windows relies on the user profile directory's inherited ACLs instead of POSIX modes.
|
||||
async function restrictToOwner(filename: string) {
|
||||
if (process.platform === "win32") return
|
||||
// Opening and closing an existing database can release another connection's POSIX locks.
|
||||
// Create missing files synchronously so another fiber cannot open one before it is restricted.
|
||||
if (!existsSync(filename)) closeSync(openSync(filename, "a", 0o600))
|
||||
await chmod(filename, 0o600)
|
||||
await Promise.all(
|
||||
[`${filename}-wal`, `${filename}-shm`].map((file) =>
|
||||
chmod(file, 0o600).catch((error: NodeJS.ErrnoException) => {
|
||||
if (error.code !== "ENOENT") throw error
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// The database service over an injected SqlClient, for runtimes that receive
|
||||
// database storage instead of opening a filesystem path. Any client provided
|
||||
// here still goes through the pragma guards and migrations; Global is required
|
||||
|
||||
@@ -149,6 +149,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
||||
responseFormat,
|
||||
}: LanguageModelV3CallOptions) {
|
||||
const warnings: SharedV3Warning[] = []
|
||||
const modelConfig = getResponsesModelConfig(this.modelId)
|
||||
|
||||
if (topK != null) {
|
||||
warnings.push({ type: "unsupported", feature: "topK" })
|
||||
@@ -185,7 +186,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
||||
|
||||
const { input, warnings: inputWarnings } = await convertToOpenAIResponsesInput({
|
||||
prompt,
|
||||
systemMessageMode: "system",
|
||||
systemMessageMode: modelConfig.systemMessageMode,
|
||||
fileIdPrefixes: this.config.fileIdPrefixes,
|
||||
store,
|
||||
})
|
||||
@@ -285,18 +286,84 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
|
||||
safety_identifier: openaiOptions?.safetyIdentifier,
|
||||
top_logprobs: topLogprobs,
|
||||
|
||||
...((openaiOptions?.reasoningEffort != null || openaiOptions?.reasoningSummary != null) && {
|
||||
reasoning: {
|
||||
...(openaiOptions?.reasoningEffort != null && {
|
||||
effort: openaiOptions.reasoningEffort,
|
||||
}),
|
||||
...(openaiOptions?.reasoningSummary != null && {
|
||||
summary: openaiOptions.reasoningSummary,
|
||||
}),
|
||||
},
|
||||
// model-specific settings:
|
||||
...(modelConfig.isReasoningModel &&
|
||||
(openaiOptions?.reasoningEffort != null || openaiOptions?.reasoningSummary != null) && {
|
||||
reasoning: {
|
||||
...(openaiOptions?.reasoningEffort != null && {
|
||||
effort: openaiOptions.reasoningEffort,
|
||||
}),
|
||||
...(openaiOptions?.reasoningSummary != null && {
|
||||
summary: openaiOptions.reasoningSummary,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
...(modelConfig.requiredAutoTruncation && {
|
||||
truncation: "auto",
|
||||
}),
|
||||
}
|
||||
|
||||
if (modelConfig.isReasoningModel) {
|
||||
// remove unsupported settings for reasoning models
|
||||
// see https://platform.openai.com/docs/guides/reasoning#limitations
|
||||
if (baseArgs.temperature != null) {
|
||||
baseArgs.temperature = undefined
|
||||
warnings.push({
|
||||
type: "unsupported",
|
||||
feature: "temperature",
|
||||
details: "temperature is not supported for reasoning models",
|
||||
})
|
||||
}
|
||||
|
||||
if (baseArgs.top_p != null) {
|
||||
baseArgs.top_p = undefined
|
||||
warnings.push({
|
||||
type: "unsupported",
|
||||
feature: "topP",
|
||||
details: "topP is not supported for reasoning models",
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (openaiOptions?.reasoningEffort != null) {
|
||||
warnings.push({
|
||||
type: "unsupported",
|
||||
feature: "reasoningEffort",
|
||||
details: "reasoningEffort is not supported for non-reasoning models",
|
||||
})
|
||||
}
|
||||
|
||||
if (openaiOptions?.reasoningSummary != null) {
|
||||
warnings.push({
|
||||
type: "unsupported",
|
||||
feature: "reasoningSummary",
|
||||
details: "reasoningSummary is not supported for non-reasoning models",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Validate flex processing support
|
||||
if (openaiOptions?.serviceTier === "flex" && !modelConfig.supportsFlexProcessing) {
|
||||
warnings.push({
|
||||
type: "unsupported",
|
||||
feature: "serviceTier",
|
||||
details: "flex processing is only available for o3, o4-mini, and gpt-5 models",
|
||||
})
|
||||
// Remove from args if not supported
|
||||
baseArgs.service_tier = undefined
|
||||
}
|
||||
|
||||
// Validate priority processing support
|
||||
if (openaiOptions?.serviceTier === "priority" && !modelConfig.supportsPriorityProcessing) {
|
||||
warnings.push({
|
||||
type: "unsupported",
|
||||
feature: "serviceTier",
|
||||
details:
|
||||
"priority processing is only available for supported models (gpt-4, gpt-5, gpt-5-mini, o3, o4-mini) and requires Enterprise access. gpt-5-nano is not supported",
|
||||
})
|
||||
// Remove from args if not supported
|
||||
baseArgs.service_tier = undefined
|
||||
}
|
||||
|
||||
return {
|
||||
getHostedToolName,
|
||||
args: {
|
||||
@@ -1591,6 +1658,69 @@ function isErrorChunk(chunk: z.infer<typeof openaiResponsesChunkSchema>): chunk
|
||||
return chunk.type === "error"
|
||||
}
|
||||
|
||||
type ResponsesModelConfig = {
|
||||
isReasoningModel: boolean
|
||||
systemMessageMode: "remove" | "system" | "developer"
|
||||
requiredAutoTruncation: boolean
|
||||
supportsFlexProcessing: boolean
|
||||
supportsPriorityProcessing: boolean
|
||||
}
|
||||
|
||||
function getResponsesModelConfig(modelId: string): ResponsesModelConfig {
|
||||
const supportsFlexProcessing =
|
||||
modelId.startsWith("o3") ||
|
||||
modelId.startsWith("o4-mini") ||
|
||||
(modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-chat"))
|
||||
const supportsPriorityProcessing =
|
||||
modelId.startsWith("gpt-4") ||
|
||||
modelId.startsWith("gpt-5-mini") ||
|
||||
(modelId.startsWith("gpt-5") && !modelId.startsWith("gpt-5-nano") && !modelId.startsWith("gpt-5-chat")) ||
|
||||
modelId.startsWith("o3") ||
|
||||
modelId.startsWith("o4-mini")
|
||||
const defaults = {
|
||||
requiredAutoTruncation: false,
|
||||
systemMessageMode: "system" as const,
|
||||
supportsFlexProcessing,
|
||||
supportsPriorityProcessing,
|
||||
}
|
||||
|
||||
// gpt-5-chat models are non-reasoning
|
||||
if (modelId.startsWith("gpt-5-chat")) {
|
||||
return {
|
||||
...defaults,
|
||||
isReasoningModel: false,
|
||||
}
|
||||
}
|
||||
|
||||
// o series reasoning models:
|
||||
if (
|
||||
modelId.startsWith("o") ||
|
||||
modelId.startsWith("gpt-5") ||
|
||||
modelId.startsWith("codex-") ||
|
||||
modelId.startsWith("computer-use")
|
||||
) {
|
||||
if (modelId.startsWith("o1-mini") || modelId.startsWith("o1-preview")) {
|
||||
return {
|
||||
...defaults,
|
||||
isReasoningModel: true,
|
||||
systemMessageMode: "remove",
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...defaults,
|
||||
isReasoningModel: true,
|
||||
systemMessageMode: "developer",
|
||||
}
|
||||
}
|
||||
|
||||
// gpt models:
|
||||
return {
|
||||
...defaults,
|
||||
isReasoningModel: false,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO AI SDK 6: use optional here instead of nullish
|
||||
const openaiResponsesProviderOptionsSchema = z.object({
|
||||
include: z
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Context,
|
||||
Duration,
|
||||
Effect,
|
||||
Equal,
|
||||
Exit,
|
||||
Fiber,
|
||||
Layer,
|
||||
@@ -189,12 +188,6 @@ export interface Interface extends State.Transformable<Editor> {
|
||||
) => Effect.Effect<void>
|
||||
/** Removes a stored credential connection. */
|
||||
readonly remove: (credentialID: Credential.ID) => Effect.Effect<void>
|
||||
/** Reports or clears a runtime problem with a connection; `get` and `list` project it onto the connection. */
|
||||
readonly status: (input: {
|
||||
readonly integrationID: ID
|
||||
readonly connection: IntegrationConnection.Info
|
||||
readonly status: IntegrationConnection.Status | undefined
|
||||
}) => Effect.Effect<void>
|
||||
}
|
||||
readonly oauth: {
|
||||
/** Starts a stateful OAuth attempt. */
|
||||
@@ -286,10 +279,6 @@ const layer = Layer.effect(
|
||||
const scope = yield* Scope.Scope
|
||||
const attempts = SynchronizedRef.makeUnsafe(new Map<AttemptID, AttemptEntry>())
|
||||
const commandAttempts = SynchronizedRef.makeUnsafe(new Map<AttemptID, CommandAttemptEntry>())
|
||||
// Runtime-only: statuses describe the current process's view of a connection and are not persisted.
|
||||
const statuses = new Map<string, IntegrationConnection.Status>()
|
||||
const statusKey = (integrationID: ID, connection: IntegrationConnection.Info) =>
|
||||
`${integrationID}:${IntegrationConnection.key(connection)}`
|
||||
const state = State.create<Data, Editor>({
|
||||
name: "integration",
|
||||
initial: () => ({ integrations: new Map<ID, Entry>() }),
|
||||
@@ -388,10 +377,7 @@ const layer = Layer.effect(
|
||||
name: entry.ref.name,
|
||||
...(entry.ref.metadata === undefined ? {} : { metadata: entry.ref.metadata }),
|
||||
methods: entry.methods,
|
||||
connections: connections.map((connection) => {
|
||||
const status = statuses.get(statusKey(entry.ref.id, connection))
|
||||
return status ? { ...connection, status } : connection
|
||||
}),
|
||||
connections,
|
||||
})
|
||||
|
||||
const authorize = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
|
||||
@@ -741,13 +727,6 @@ const layer = Layer.effect(
|
||||
credentials.update(credentialID, updates),
|
||||
),
|
||||
remove: Effect.fn("Integration.connection.remove")((credentialID) => credentials.remove(credentialID)),
|
||||
status: Effect.fn("Integration.connection.status")(function* (input) {
|
||||
const key = statusKey(input.integrationID, input.connection)
|
||||
if (Equal.equals(statuses.get(key), input.status)) return
|
||||
if (input.status) statuses.set(key, input.status)
|
||||
if (!input.status) statuses.delete(key)
|
||||
yield* bus.publish(Integration.Event.Updated, {})
|
||||
}),
|
||||
},
|
||||
oauth: {
|
||||
connect: connectOAuth,
|
||||
|
||||
@@ -11,9 +11,6 @@ export type EnvInfo = Connection.EnvInfo
|
||||
export const Info = Connection.Info
|
||||
export type Info = Connection.Info
|
||||
|
||||
export const Status = Connection.Status
|
||||
export type Status = Connection.Status
|
||||
|
||||
/** Identity of an access choice; labels and refreshed token values do not identify a new connection. */
|
||||
export function key(
|
||||
connection:
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
@@ -286,11 +286,7 @@ const layer = Layer.effect(
|
||||
requestID: item.request.id,
|
||||
reply: "reject",
|
||||
})
|
||||
// Feedback applies to the whole batch, so parallel asks don't end the step.
|
||||
yield* Deferred.fail(
|
||||
item.deferred,
|
||||
input.message ? new CorrectedError({ feedback: input.message }) : new DeclinedError(),
|
||||
)
|
||||
yield* Deferred.fail(item.deferred, new DeclinedError())
|
||||
pending.delete(id)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -345,15 +345,6 @@ export const make = Effect.fn("PluginHost.make")(function* (
|
||||
integration.connection.resolve(
|
||||
connection.type === "credential" ? { ...connection, id: Credential.ID.make(connection.id) } : connection,
|
||||
),
|
||||
status: (input) =>
|
||||
integration.connection.status({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
connection:
|
||||
input.connection.type === "credential"
|
||||
? { ...input.connection, id: Credential.ID.make(input.connection.id) }
|
||||
: input.connection,
|
||||
status: input.status,
|
||||
}),
|
||||
},
|
||||
transform: (callback) =>
|
||||
integration.transform((editor) => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { AmazonBedrockPlugin } from "./provider/amazon-bedrock.js"
|
||||
import { AzurePlugin } from "./provider/azure.js"
|
||||
import { CerebrasPlugin } from "./provider/cerebras.js"
|
||||
import { ChatGPTPlugin } from "./provider/chatgpt.js"
|
||||
import { CloudflareAIGatewayPlugin } from "./provider/cloudflare-ai-gateway.js"
|
||||
import { CloudflareWorkersAIPlugin } from "./provider/cloudflare-workers-ai.js"
|
||||
import { CoherePlugin } from "./provider/cohere.js"
|
||||
@@ -36,7 +35,6 @@ export const ProviderPlugins: PluginInternal.InternalPlugin[] = [
|
||||
AmazonBedrockPlugin,
|
||||
AzurePlugin,
|
||||
CerebrasPlugin,
|
||||
ChatGPTPlugin,
|
||||
CloudflareAIGatewayPlugin,
|
||||
CloudflareWorkersAIPlugin,
|
||||
CoherePlugin,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user