mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-14 23:38:23 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e7b5f1701 |
@@ -173,6 +173,18 @@ export const buildPrompt = (input: { readonly previousSummary?: string; readonly
|
||||
].join("\n\n")
|
||||
}
|
||||
|
||||
export const buildReplayPrompt = (previousSummary?: string) => {
|
||||
const instruction =
|
||||
"Create a new anchored summary from the conversation messages above so another coding agent can continue the work."
|
||||
if (!previousSummary) return [instruction, SUMMARY_TEMPLATE].join("\n\n")
|
||||
return [
|
||||
instruction,
|
||||
`Here is the summary of the conversation before the messages above:\n\n<prior-summary>\n${previousSummary}\n</prior-summary>`,
|
||||
SUMMARY_UPDATE_INSTRUCTIONS,
|
||||
SUMMARY_TEMPLATE,
|
||||
].join("\n\n")
|
||||
}
|
||||
|
||||
export const make = (dependencies: Dependencies) => {
|
||||
const config = settings(dependencies.config)
|
||||
const compactAfterOverflow = Effect.fn("SessionCompaction.compactAfterOverflow")(function* (input: Input) {
|
||||
|
||||
@@ -216,21 +216,6 @@ const layer = Layer.effect(
|
||||
mode: "subagent",
|
||||
native: true,
|
||||
},
|
||||
compaction: {
|
||||
name: "compaction",
|
||||
mode: "primary",
|
||||
native: true,
|
||||
hidden: true,
|
||||
prompt: PROMPT_COMPACTION,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
user,
|
||||
),
|
||||
options: {},
|
||||
},
|
||||
title: {
|
||||
name: "title",
|
||||
mode: "primary",
|
||||
@@ -270,6 +255,26 @@ const layer = Layer.effect(
|
||||
continue
|
||||
}
|
||||
let item = agents[key]
|
||||
// The compaction agent is not registered by default: compaction inherits
|
||||
// the session's active agent so its request stays prefix-identical for
|
||||
// prompt caching. An explicit `agent.compaction` config opts into the
|
||||
// legacy dedicated agent, seeded here so overrides merge as before.
|
||||
if (!item && key === "compaction")
|
||||
item = agents[key] = {
|
||||
name: "compaction",
|
||||
mode: "primary",
|
||||
native: true,
|
||||
hidden: true,
|
||||
prompt: PROMPT_COMPACTION,
|
||||
permission: Permission.merge(
|
||||
defaults,
|
||||
Permission.fromConfig({
|
||||
"*": "deny",
|
||||
}),
|
||||
user,
|
||||
),
|
||||
options: {},
|
||||
}
|
||||
if (!item)
|
||||
item = agents[key] = {
|
||||
name: key,
|
||||
|
||||
@@ -37,12 +37,10 @@ function base64UrlEncode(buffer: ArrayBuffer): string {
|
||||
|
||||
export interface IdTokenClaims {
|
||||
chatgpt_account_id?: string
|
||||
chatgpt_compute_residency?: string
|
||||
organizations?: Array<{ id: string }>
|
||||
email?: string
|
||||
"https://api.openai.com/auth"?: {
|
||||
chatgpt_account_id?: string
|
||||
chatgpt_compute_residency?: string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,14 +75,6 @@ export function extractAccountId(tokens: TokenResponse): string | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function extractResidency(token: string): string | undefined {
|
||||
const claims = parseJwtClaims(token)
|
||||
const residency =
|
||||
claims?.["https://api.openai.com/auth"]?.chatgpt_compute_residency ?? claims?.chatgpt_compute_residency
|
||||
if (!residency || residency === "no_constraint") return undefined
|
||||
return residency
|
||||
}
|
||||
|
||||
function buildAuthorizeUrl(redirectUri: string, pkce: PkceCodes, state: string): string {
|
||||
const params = new URLSearchParams({
|
||||
response_type: "code",
|
||||
@@ -421,12 +411,10 @@ export async function CodexAuthPlugin(input: PluginInput, options: CodexAuthPlug
|
||||
requestInput instanceof URL
|
||||
? requestInput
|
||||
: new URL(typeof requestInput === "string" ? requestInput : requestInput.url)
|
||||
const rewrite = parsed.pathname.includes("/v1/responses") || parsed.pathname.includes("/chat/completions")
|
||||
const url = rewrite ? new URL(codexApiEndpoint) : parsed
|
||||
if (rewrite) {
|
||||
const residency = extractResidency(currentAuth.access)
|
||||
if (residency) headers.set("x-openai-internal-codex-residency", residency)
|
||||
}
|
||||
const url =
|
||||
parsed.pathname.includes("/v1/responses") || parsed.pathname.includes("/chat/completions")
|
||||
? new URL(codexApiEndpoint)
|
||||
: parsed
|
||||
|
||||
const requestInit = {
|
||||
...init,
|
||||
|
||||
@@ -20,7 +20,7 @@ import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { buildPrompt } from "@opencode-ai/core/session/compaction"
|
||||
import { buildPrompt, buildReplayPrompt } from "@opencode-ai/core/session/compaction"
|
||||
import { SessionCompactionEvent } from "@opencode-ai/schema/session-compaction-event"
|
||||
|
||||
export const Event = SessionCompactionEvent
|
||||
@@ -162,6 +162,15 @@ function splitTurn(input: {
|
||||
})
|
||||
}
|
||||
|
||||
export type Run = (input: {
|
||||
user: SessionV1.User
|
||||
agent: Agent.Info
|
||||
model: Provider.Model
|
||||
processor: SessionProcessor.Handle
|
||||
messages: SessionV1.WithParts[]
|
||||
prompt: string
|
||||
}) => Effect.Effect<SessionProcessor.Result>
|
||||
|
||||
export interface Interface {
|
||||
readonly isOverflow: (input: {
|
||||
tokens: SessionV1.Assistant["tokens"]
|
||||
@@ -174,6 +183,7 @@ export interface Interface {
|
||||
sessionID: SessionID
|
||||
auto: boolean
|
||||
overflow?: boolean
|
||||
run?: Run
|
||||
}) => Effect.Effect<"continue" | "stop">
|
||||
readonly create: (input: {
|
||||
sessionID: SessionID
|
||||
@@ -322,6 +332,7 @@ const layer = Layer.effect(
|
||||
sessionID: SessionID
|
||||
auto: boolean
|
||||
overflow?: boolean
|
||||
run?: Run
|
||||
}) {
|
||||
const parent = input.messages.findLast((m) => m.info.id === input.parentID)
|
||||
if (!parent || parent.info.role !== "user") {
|
||||
@@ -355,11 +366,13 @@ const layer = Layer.effect(
|
||||
}
|
||||
}
|
||||
|
||||
const agent = yield* agents.get("compaction")
|
||||
const model = agent.model
|
||||
? yield* provider.getModel(agent.model.providerID, agent.model.modelID).pipe(Effect.orDie)
|
||||
: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID).pipe(Effect.orDie)
|
||||
const cfg = yield* config.get()
|
||||
const configured = cfg.agent?.compaction !== undefined
|
||||
const agent = yield* agents.get(configured ? "compaction" : userMessage.agent)
|
||||
const model =
|
||||
configured && agent.model
|
||||
? yield* provider.getModel(agent.model.providerID, agent.model.modelID).pipe(Effect.orDie)
|
||||
: yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID).pipe(Effect.orDie)
|
||||
const history = compactionPart && messages.at(-1)?.info.id === input.parentID ? messages.slice(0, -1) : messages
|
||||
const prior = completedCompactions(history)
|
||||
const hidden = new Set(prior.flatMap((item) => [item.userIndex, item.assistantIndex]))
|
||||
@@ -377,18 +390,16 @@ const layer = Layer.effect(
|
||||
)
|
||||
const msgs = structuredClone(selected.head)
|
||||
yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
|
||||
const conversation = msgs.map(serialize).filter(Boolean).join("\n\n")
|
||||
const nextPrompt =
|
||||
// A configured compaction agent keeps the legacy serialized-transcript path.
|
||||
const run = configured ? undefined : input.run
|
||||
const conversation = run ? "" : msgs.map(serialize).filter(Boolean).join("\n\n")
|
||||
const nextPrompt = [
|
||||
compacting.prompt ??
|
||||
[
|
||||
buildPrompt({
|
||||
previousSummary,
|
||||
context: [conversation],
|
||||
}),
|
||||
...compacting.context,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
(run ? buildReplayPrompt(previousSummary) : buildPrompt({ previousSummary, context: [conversation] })),
|
||||
...compacting.context,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
const ctx = yield* InstanceState.context
|
||||
const msg: SessionV1.Assistant = {
|
||||
id: MessageID.ascending(),
|
||||
@@ -422,30 +433,43 @@ const layer = Layer.effect(
|
||||
sessionID: input.sessionID,
|
||||
model,
|
||||
})
|
||||
const result = yield* processor.process({
|
||||
user: userMessage,
|
||||
agent,
|
||||
sessionID: input.sessionID,
|
||||
tools: {},
|
||||
system: [],
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
// The synthetic compaction marker carries no per-turn system/tools settings.
|
||||
const active = msgs.findLast(
|
||||
(message) => message.info.role === "user" && !message.parts.some((part) => part.type === "compaction"),
|
||||
)
|
||||
const result = run
|
||||
? yield* run({
|
||||
user: active?.info.role === "user" ? active.info : userMessage,
|
||||
agent,
|
||||
model,
|
||||
processor,
|
||||
messages: msgs,
|
||||
prompt: nextPrompt,
|
||||
})
|
||||
: yield* processor.process({
|
||||
user: userMessage,
|
||||
agent,
|
||||
sessionID: input.sessionID,
|
||||
tools: {},
|
||||
system: [],
|
||||
messages: [
|
||||
{
|
||||
type: "text",
|
||||
text: [
|
||||
nextPrompt,
|
||||
...(compacting.prompt ? ["The following is the conversation history:", conversation] : []),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n"),
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: [
|
||||
nextPrompt,
|
||||
...(compacting.prompt ? ["The following is the conversation history:", conversation] : []),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
model,
|
||||
})
|
||||
model,
|
||||
})
|
||||
|
||||
if (result === "compact") {
|
||||
processor.message.error = new SessionV1.ContextOverflowError({
|
||||
|
||||
@@ -149,6 +149,45 @@ const layer = Layer.effect(
|
||||
} satisfies TaskPromptOps
|
||||
})
|
||||
|
||||
// One assembly path keeps compaction requests prefix-identical to normal turns for prompt caching.
|
||||
const prepare = Effect.fn("SessionPrompt.prepare")(function* (input: {
|
||||
session: Session.Info
|
||||
agent: Agent.Info
|
||||
model: Provider.Model
|
||||
processor: SessionProcessor.Handle
|
||||
messages: SessionV1.WithParts[]
|
||||
}) {
|
||||
const lastUserMsg = input.messages.findLast((message) => message.info.role === "user")
|
||||
const tools = yield* SessionTools.resolve({
|
||||
agent: input.agent,
|
||||
session: input.session,
|
||||
model: input.model,
|
||||
processor: input.processor,
|
||||
bypassAgentCheck: lastUserMsg?.parts.some((part) => part.type === "agent") ?? false,
|
||||
messages: input.messages,
|
||||
promptOps: yield* ops(),
|
||||
}).pipe(
|
||||
Effect.provideService(Plugin.Service, plugin),
|
||||
Effect.provideService(Permission.Service, permission),
|
||||
Effect.provideService(ToolRegistry.Service, registry),
|
||||
Effect.provideService(MCP.Service, mcp),
|
||||
Effect.provideService(Truncate.Service, truncate),
|
||||
Effect.provideService(RuntimeFlags.Service, flags),
|
||||
)
|
||||
const [skills, env, instructions, mcpInstructions, modelMessages] = yield* Effect.all([
|
||||
sys.skills(input.agent),
|
||||
sys.environment(input.model),
|
||||
instruction.system().pipe(Effect.orDie),
|
||||
sys.mcp(input.agent, input.session.permission),
|
||||
MessageV2.toModelMessagesEffect(input.messages, input.model),
|
||||
])
|
||||
return {
|
||||
tools,
|
||||
modelMessages,
|
||||
system: [...env, ...instructions, ...(mcpInstructions ? [mcpInstructions] : []), ...(skills ? [skills] : [])],
|
||||
}
|
||||
})
|
||||
|
||||
const cancel = Effect.fn("SessionPrompt.cancel")(function* (sessionID: SessionID) {
|
||||
yield* Effect.logInfo("cancel", { "session.id": sessionID })
|
||||
yield* state.cancel(sessionID)
|
||||
@@ -1153,6 +1192,21 @@ const layer = Layer.effect(
|
||||
sessionID,
|
||||
auto: task.auto,
|
||||
overflow: task.overflow,
|
||||
run: ({ user, agent, model, processor, messages, prompt }) =>
|
||||
Effect.gen(function* () {
|
||||
const prepared = yield* prepare({ session, agent, model, processor, messages })
|
||||
return yield* processor.process({
|
||||
user,
|
||||
agent,
|
||||
permission: session.permission,
|
||||
sessionID,
|
||||
parentSessionID: session.parentID,
|
||||
system: prepared.system,
|
||||
messages: [...prepared.modelMessages, { role: "user", content: prompt }],
|
||||
tools: prepared.tools,
|
||||
model,
|
||||
})
|
||||
}),
|
||||
})
|
||||
if (result === "stop") break
|
||||
continue
|
||||
@@ -1219,68 +1273,34 @@ const layer = Layer.effect(
|
||||
.pipe(Effect.onInterrupt(() => finalizeInterruptedAssistant))
|
||||
|
||||
const outcome: "break" | "continue" = yield* Effect.gen(function* () {
|
||||
const lastUserMsg = msgs.findLast((m) => m.info.role === "user")
|
||||
const bypassAgentCheck = lastUserMsg?.parts.some((p) => p.type === "agent") ?? false
|
||||
const promptOps = yield* ops()
|
||||
|
||||
const tools = yield* SessionTools.resolve({
|
||||
agent,
|
||||
session,
|
||||
model,
|
||||
processor: handle,
|
||||
bypassAgentCheck,
|
||||
messages: msgs,
|
||||
promptOps,
|
||||
}).pipe(
|
||||
Effect.provideService(Plugin.Service, plugin),
|
||||
Effect.provideService(Permission.Service, permission),
|
||||
Effect.provideService(ToolRegistry.Service, registry),
|
||||
Effect.provideService(MCP.Service, mcp),
|
||||
Effect.provideService(Truncate.Service, truncate),
|
||||
Effect.provideService(RuntimeFlags.Service, flags),
|
||||
)
|
||||
|
||||
if (lastUser.format?.type === "json_schema") {
|
||||
tools["StructuredOutput"] = createStructuredOutputTool({
|
||||
schema: lastUser.format.schema,
|
||||
onSuccess(output) {
|
||||
structured = output
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (step === 1)
|
||||
yield* summary.summarize({ sessionID, messageID: lastUser.id }).pipe(Effect.ignore, Effect.forkIn(scope))
|
||||
|
||||
yield* plugin.trigger("experimental.chat.messages.transform", {}, { messages: msgs })
|
||||
|
||||
const [skills, env, instructions, mcpInstructions, modelMsgs] = yield* Effect.all([
|
||||
sys.skills(agent),
|
||||
sys.environment(model),
|
||||
instruction.system().pipe(Effect.orDie),
|
||||
sys.mcp(agent, session.permission),
|
||||
MessageV2.toModelMessagesEffect(msgs, model),
|
||||
])
|
||||
const system = [
|
||||
...env,
|
||||
...instructions,
|
||||
...(mcpInstructions ? [mcpInstructions] : []),
|
||||
...(skills ? [skills] : []),
|
||||
]
|
||||
const prepared = yield* prepare({ session, agent, model, processor: handle, messages: msgs })
|
||||
const format = lastUser.format ?? { type: "text" as const }
|
||||
if (format.type === "json_schema") system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
|
||||
if (format.type === "json_schema") {
|
||||
prepared.system.push(STRUCTURED_OUTPUT_SYSTEM_PROMPT)
|
||||
prepared.tools["StructuredOutput"] = createStructuredOutputTool({
|
||||
schema: format.schema,
|
||||
onSuccess(output) {
|
||||
structured = output
|
||||
},
|
||||
})
|
||||
}
|
||||
const result = yield* handle.process({
|
||||
user: lastUser,
|
||||
agent,
|
||||
permission: session.permission,
|
||||
sessionID,
|
||||
parentSessionID: session.parentID,
|
||||
system,
|
||||
system: prepared.system,
|
||||
messages: [
|
||||
...modelMsgs,
|
||||
...prepared.modelMessages,
|
||||
...(isLastStep ? [{ role: "assistant" as const, content: MAX_STEPS_PROMPT }] : []),
|
||||
],
|
||||
tools,
|
||||
tools: prepared.tools,
|
||||
model,
|
||||
toolChoice: format.type === "json_schema" ? "required" : undefined,
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ it.instance("returns default native agents when no config", () =>
|
||||
expect(names).toContain("plan")
|
||||
expect(names).toContain("general")
|
||||
expect(names).toContain("explore")
|
||||
expect(names).toContain("compaction")
|
||||
expect(names).not.toContain("compaction")
|
||||
expect(names).toContain("title")
|
||||
expect(names).toContain("summary")
|
||||
}),
|
||||
@@ -170,17 +170,34 @@ it.instance("general agent denies todo tools", () =>
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance("compaction agent denies all permissions", () =>
|
||||
it.instance("compaction agent is absent without configuration", () =>
|
||||
Effect.gen(function* () {
|
||||
const compaction = yield* load((svc) => svc.get("compaction"))
|
||||
expect(compaction).toBeDefined()
|
||||
expect(compaction?.hidden).toBe(true)
|
||||
expect(evalPerm(compaction, "bash")).toBe("deny")
|
||||
expect(evalPerm(compaction, "edit")).toBe("deny")
|
||||
expect(evalPerm(compaction, "read")).toBe("deny")
|
||||
expect(compaction).toBeUndefined()
|
||||
}),
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"configured compaction agent keeps native defaults",
|
||||
() =>
|
||||
Effect.gen(function* () {
|
||||
const compaction = yield* load((svc) => svc.get("compaction"))
|
||||
expect(compaction).toBeDefined()
|
||||
expect(compaction?.native).toBe(true)
|
||||
expect(compaction?.hidden).toBe(true)
|
||||
expect(evalPerm(compaction, "bash")).toBe("deny")
|
||||
expect(evalPerm(compaction, "edit")).toBe("deny")
|
||||
expect(evalPerm(compaction, "read")).toBe("deny")
|
||||
}),
|
||||
{
|
||||
config: {
|
||||
agent: {
|
||||
compaction: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"custom agent from config creates new agent",
|
||||
() =>
|
||||
@@ -710,6 +727,9 @@ it.instance(
|
||||
{
|
||||
config: {
|
||||
default_agent: "compaction",
|
||||
agent: {
|
||||
compaction: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { createServer, type IncomingMessage } from "node:http"
|
||||
import { type AddressInfo } from "node:net"
|
||||
import { WebSocketServer } from "ws"
|
||||
import {
|
||||
CodexAuthPlugin,
|
||||
parseJwtClaims,
|
||||
extractAccountIdFromClaims,
|
||||
extractAccountId,
|
||||
extractResidency,
|
||||
renderOAuthError,
|
||||
type IdTokenClaims,
|
||||
} from "../../src/plugin/openai/codex"
|
||||
@@ -135,69 +131,6 @@ describe("plugin.codex", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("extractResidency", () => {
|
||||
test("extracts compute residency from the namespaced auth claims", () => {
|
||||
expect(
|
||||
extractResidency(
|
||||
createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "eu" },
|
||||
}),
|
||||
),
|
||||
).toBe("eu")
|
||||
})
|
||||
|
||||
test("falls back to a root compute residency claim", () => {
|
||||
expect(extractResidency(createTestJwt({ chatgpt_compute_residency: "us" }))).toBe("us")
|
||||
})
|
||||
|
||||
test("supports compute residency values without maintaining a region list", () => {
|
||||
expect(
|
||||
extractResidency(
|
||||
createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "ae" },
|
||||
}),
|
||||
),
|
||||
).toBe("ae")
|
||||
expect(
|
||||
extractResidency(
|
||||
createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "future-region_1" },
|
||||
}),
|
||||
),
|
||||
).toBe("future-region_1")
|
||||
})
|
||||
|
||||
test("ignores unconstrained and data residency values", () => {
|
||||
expect(
|
||||
extractResidency(
|
||||
createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "no_constraint" },
|
||||
}),
|
||||
),
|
||||
).toBeUndefined()
|
||||
expect(
|
||||
extractResidency(
|
||||
createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_data_residency: "gb" },
|
||||
}),
|
||||
),
|
||||
).toBeUndefined()
|
||||
expect(extractResidency(createTestJwt({ chatgpt_compute_residency: "" }))).toBeUndefined()
|
||||
expect(extractResidency("not-a-jwt")).toBeUndefined()
|
||||
})
|
||||
|
||||
test("prefers a namespaced unconstrained value over a root residency", () => {
|
||||
expect(
|
||||
extractResidency(
|
||||
createTestJwt({
|
||||
chatgpt_compute_residency: "eu",
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "no_constraint" },
|
||||
}),
|
||||
),
|
||||
).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
test("installs websocket transport only when experimental websockets are enabled", async () => {
|
||||
const disabled = await CodexAuthPlugin({} as never)
|
||||
const enabled = await CodexAuthPlugin({} as never, { experimentalWebSockets: true })
|
||||
@@ -216,73 +149,6 @@ describe("plugin.codex", () => {
|
||||
await enabled.dispose?.()
|
||||
})
|
||||
|
||||
test("sends token residency only to the ChatGPT Codex backend", async () => {
|
||||
const requests: Array<{ path: string; residency: string | null }> = []
|
||||
using server = Bun.serve({
|
||||
port: 0,
|
||||
fetch(request) {
|
||||
requests.push({
|
||||
path: new URL(request.url).pathname,
|
||||
residency: request.headers.get("x-openai-internal-codex-residency"),
|
||||
})
|
||||
return new Response("{}")
|
||||
},
|
||||
})
|
||||
const hooks = await CodexAuthPlugin({} as never, {
|
||||
codexApiEndpoint: new URL("/backend-api/codex/responses", server.url).toString(),
|
||||
})
|
||||
const loaded = await hooks.auth!.loader!(
|
||||
async () =>
|
||||
({
|
||||
type: "oauth",
|
||||
refresh: "refresh",
|
||||
access: createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "eu" },
|
||||
}),
|
||||
expires: Date.now() + 60_000,
|
||||
}) as never,
|
||||
{} as never,
|
||||
)
|
||||
|
||||
await loaded.fetch!("https://api.openai.com/v1/responses")
|
||||
await loaded.fetch!(new URL("/other", server.url))
|
||||
|
||||
expect(requests).toEqual([
|
||||
{ path: "/backend-api/codex/responses", residency: "eu" },
|
||||
{ path: "/other", residency: null },
|
||||
])
|
||||
})
|
||||
|
||||
test("sends token residency through the WebSocket transport", async () => {
|
||||
await using server = await createCodexWebSocketServer()
|
||||
const hooks = await CodexAuthPlugin({} as never, {
|
||||
codexApiEndpoint: server.url,
|
||||
experimentalWebSockets: true,
|
||||
})
|
||||
const loaded = await hooks.auth!.loader!(
|
||||
async () =>
|
||||
({
|
||||
type: "oauth",
|
||||
refresh: "refresh",
|
||||
access: createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "eu" },
|
||||
}),
|
||||
expires: Date.now() + 60_000,
|
||||
}) as never,
|
||||
{} as never,
|
||||
)
|
||||
|
||||
const response = await loaded.fetch!("https://api.openai.com/v1/responses", {
|
||||
method: "POST",
|
||||
headers: { "session-id": "session-1" },
|
||||
body: JSON.stringify({ stream: true, input: "hi" }),
|
||||
})
|
||||
|
||||
expect(await response.text()).toContain("data: [DONE]")
|
||||
expect(server.headers()?.["x-openai-internal-codex-residency"]).toBe("eu")
|
||||
await hooks.dispose?.()
|
||||
})
|
||||
|
||||
test("filters unsupported modes and uses Codex context limits for OAuth GPT models", async () => {
|
||||
const hooks = await CodexAuthPlugin({} as never)
|
||||
const limit = { context: 1_050_000, input: 922_000, output: 128_000 }
|
||||
@@ -327,9 +193,6 @@ describe("plugin.codex", () => {
|
||||
})
|
||||
|
||||
test("deduplicates concurrent Codex token refreshes", async () => {
|
||||
const refreshedAccess = createTestJwt({
|
||||
"https://api.openai.com/auth": { chatgpt_compute_residency: "eu" },
|
||||
})
|
||||
let auth = {
|
||||
type: "oauth" as const,
|
||||
refresh: "refresh-old",
|
||||
@@ -344,7 +207,7 @@ describe("plugin.codex", () => {
|
||||
resolveRefresh = resolve
|
||||
})
|
||||
let refreshRequests = 0
|
||||
const apiRequests: { authorization: string | null; accountId: string | null; residency: string | null }[] = []
|
||||
const apiRequests: { authorization: string | null; accountId: string | null }[] = []
|
||||
|
||||
using server = Bun.serve({
|
||||
port: 0,
|
||||
@@ -356,7 +219,7 @@ describe("plugin.codex", () => {
|
||||
await refreshReady
|
||||
return Response.json({
|
||||
id_token: createTestJwt({ chatgpt_account_id: "acc-123" }),
|
||||
access_token: refreshedAccess,
|
||||
access_token: "access-new",
|
||||
refresh_token: "refresh-new",
|
||||
expires_in: 3600,
|
||||
})
|
||||
@@ -366,7 +229,6 @@ describe("plugin.codex", () => {
|
||||
apiRequests.push({
|
||||
authorization: request.headers.get("authorization"),
|
||||
accountId: request.headers.get("ChatGPT-Account-Id"),
|
||||
residency: request.headers.get("x-openai-internal-codex-residency"),
|
||||
})
|
||||
return new Response("{}", { status: 200 })
|
||||
}
|
||||
@@ -419,11 +281,11 @@ describe("plugin.codex", () => {
|
||||
expect(refreshRequests).toBe(1)
|
||||
expect(authUpdates).toHaveLength(1)
|
||||
expect(authUpdates[0]?.body.refresh).toBe("refresh-new")
|
||||
expect(authUpdates[0]?.body.access).toBe(refreshedAccess)
|
||||
expect(authUpdates[0]?.body.access).toBe("access-new")
|
||||
expect(authUpdates[0]?.body.accountId).toBe("acc-123")
|
||||
expect(apiRequests).toEqual([
|
||||
{ authorization: `Bearer ${refreshedAccess}`, accountId: "acc-123", residency: "eu" },
|
||||
{ authorization: `Bearer ${refreshedAccess}`, accountId: "acc-123", residency: "eu" },
|
||||
{ authorization: "Bearer access-new", accountId: "acc-123" },
|
||||
{ authorization: "Bearer access-new", accountId: "acc-123" },
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -435,29 +297,3 @@ async function waitFor(predicate: () => boolean) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1))
|
||||
}
|
||||
}
|
||||
|
||||
async function createCodexWebSocketServer() {
|
||||
let headers: IncomingMessage["headers"] | undefined
|
||||
const server = createServer()
|
||||
const sockets = new WebSocketServer({ server })
|
||||
sockets.on("connection", (socket, request) => {
|
||||
headers = request.headers
|
||||
socket.once("message", () => {
|
||||
socket.send(JSON.stringify({ type: "response.completed", response: { id: "resp_123" } }))
|
||||
})
|
||||
})
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.once("error", reject)
|
||||
server.listen(0, "127.0.0.1", resolve)
|
||||
})
|
||||
const address = server.address() as AddressInfo
|
||||
return {
|
||||
url: `http://127.0.0.1:${address.port}/backend-api/codex/responses`,
|
||||
headers: () => headers,
|
||||
async [Symbol.asyncDispose]() {
|
||||
for (const socket of sockets.clients) socket.terminate()
|
||||
sockets.close()
|
||||
server.close()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,18 +17,13 @@ describe("plugin.openai.ws", () => {
|
||||
|
||||
const socket = await OpenAIWebSocket.connectResponsesWebSocket({
|
||||
url: server.wsUrl,
|
||||
headers: {
|
||||
authorization: "Bearer test",
|
||||
"content-length": "123",
|
||||
"x-openai-internal-codex-residency": "eu",
|
||||
},
|
||||
headers: { authorization: "Bearer test", "content-length": "123" },
|
||||
})
|
||||
|
||||
expect(OpenAIWebSocket.toWebSocketUrl("http://example.com/v1/responses")).toBe("ws://example.com/v1/responses")
|
||||
expect(OpenAIWebSocket.toWebSocketUrl("https://example.com/v1/responses")).toBe("wss://example.com/v1/responses")
|
||||
expect(headers?.authorization).toBe("Bearer test")
|
||||
expect(headers?.["openai-beta"]).toBe(OpenAIWebSocket.PROTOCOL_HEADER)
|
||||
expect(headers?.["x-openai-internal-codex-residency"]).toBe("eu")
|
||||
expect(headers?.["content-length"]).toBeUndefined()
|
||||
socket.terminate()
|
||||
})
|
||||
|
||||
@@ -222,6 +222,11 @@ function cfg(compaction?: ConfigV1.Info["compaction"]) {
|
||||
return Layer.succeed(Config.Service, TestConfig.make({ get: () => Effect.succeed({ ...base, compaction }) }))
|
||||
}
|
||||
|
||||
function cfgAgent(agent: NonNullable<ConfigV1.Info["agent"]>) {
|
||||
const config = Schema.decodeUnknownSync(ConfigV1.Info)({ agent }) as ConfigV1.Info
|
||||
return Layer.succeed(Config.Service, TestConfig.make({ get: () => Effect.succeed(config) }))
|
||||
}
|
||||
|
||||
const defaultProvider = wide()
|
||||
const compactionTestNode = LayerNode.group([
|
||||
SessionCompaction.node,
|
||||
@@ -812,6 +817,75 @@ describe("session.compaction.prune", () => {
|
||||
})
|
||||
|
||||
describe("session.compaction.process", () => {
|
||||
itCompaction.instance(
|
||||
"inherits the last agent when compaction is not configured",
|
||||
() => {
|
||||
const stub = llm()
|
||||
let captured: LLM.StreamInput | undefined
|
||||
stub.push(reply("summary", (input) => (captured = input)))
|
||||
return Effect.gen(function* () {
|
||||
const ssn = yield* SessionNs.Service
|
||||
const session = yield* ssn.create({})
|
||||
const msg = yield* createUserMessage(session.id, "hello")
|
||||
const msgs = yield* ssn.messages({ sessionID: session.id })
|
||||
|
||||
yield* SessionCompaction.use.process({
|
||||
parentID: msg.id,
|
||||
messages: msgs,
|
||||
sessionID: session.id,
|
||||
auto: false,
|
||||
})
|
||||
|
||||
expect(captured?.agent.name).toBe("build")
|
||||
expect(captured?.agent.prompt).toBe("custom build prompt")
|
||||
expect(captured?.agent.temperature).toBe(0.37)
|
||||
expect(String(captured?.model.providerID)).toBe("test")
|
||||
expect(String(captured?.model.id)).toBe("test-model")
|
||||
}).pipe(
|
||||
withCompaction({
|
||||
llm: stub.llmLayer,
|
||||
config: cfgAgent({
|
||||
build: { prompt: "custom build prompt", temperature: 0.37, model: "missing/missing" },
|
||||
}),
|
||||
}),
|
||||
)
|
||||
},
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
itCompaction.instance(
|
||||
"preserves an explicitly configured compaction agent",
|
||||
() => {
|
||||
const stub = llm()
|
||||
let captured: LLM.StreamInput | undefined
|
||||
stub.push(reply("summary", (input) => (captured = input)))
|
||||
return Effect.gen(function* () {
|
||||
const ssn = yield* SessionNs.Service
|
||||
const session = yield* ssn.create({})
|
||||
const msg = yield* createUserMessage(session.id, "hello")
|
||||
const msgs = yield* ssn.messages({ sessionID: session.id })
|
||||
|
||||
yield* SessionCompaction.use.process({
|
||||
parentID: msg.id,
|
||||
messages: msgs,
|
||||
sessionID: session.id,
|
||||
auto: false,
|
||||
})
|
||||
|
||||
expect(captured?.agent.name).toBe("compaction")
|
||||
expect(captured?.agent.prompt).toBe("custom compaction prompt")
|
||||
expect(captured?.agent.temperature).toBe(0.81)
|
||||
expect(captured?.agent.hidden).toBe(true)
|
||||
}).pipe(
|
||||
withCompaction({
|
||||
llm: stub.llmLayer,
|
||||
config: cfgAgent({ compaction: { prompt: "custom compaction prompt", temperature: 0.81 } }),
|
||||
}),
|
||||
)
|
||||
},
|
||||
{ git: true },
|
||||
)
|
||||
|
||||
it.instance(
|
||||
"throws when parent is not a user message",
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -444,6 +444,34 @@ const boot = Effect.fn("test.boot")(function* (input?: { title?: string }) {
|
||||
|
||||
// Loop semantics
|
||||
|
||||
it.instance("default compaction reuses the normal system and tools", () =>
|
||||
Effect.gen(function* () {
|
||||
const { llm } = yield* useServerConfig(providerCfg)
|
||||
const { prompt, chat } = yield* boot()
|
||||
const compact = yield* SessionCompaction.Service
|
||||
yield* user(chat.id, "cache prefix message")
|
||||
yield* llm.text("normal response")
|
||||
yield* prompt.loop({ sessionID: chat.id })
|
||||
|
||||
yield* compact.create({ sessionID: chat.id, agent: "build", model: ref, auto: false })
|
||||
yield* llm.text("compacted summary")
|
||||
yield* prompt.loop({ sessionID: chat.id })
|
||||
|
||||
const inputs = yield* llm.inputs
|
||||
expect(inputs).toHaveLength(2)
|
||||
expect(inputs[1]?.tools).toEqual(inputs[0]?.tools)
|
||||
const normalMessages = Array.isArray(inputs[0]?.messages) ? inputs[0].messages : []
|
||||
const compactMessages = Array.isArray(inputs[1]?.messages) ? inputs[1].messages : []
|
||||
const system = (message: unknown) =>
|
||||
typeof message === "object" && message !== null && "role" in message && message.role === "system"
|
||||
expect(compactMessages.find(system)).toEqual(normalMessages.find(system))
|
||||
expect(JSON.stringify(compactMessages)).toContain("cache prefix message")
|
||||
expect(JSON.stringify(compactMessages)).toContain("normal response")
|
||||
expect(JSON.stringify(compactMessages)).toContain("Create a new anchored summary")
|
||||
expect(JSON.stringify(compactMessages)).not.toContain("[User]: cache prefix message")
|
||||
}),
|
||||
)
|
||||
|
||||
noLLMServer.instance(
|
||||
"loop exits immediately when last assistant has stop finish",
|
||||
() =>
|
||||
|
||||
@@ -86,7 +86,6 @@ OpenCode Zen هي بوابة AI تتيح لك الوصول إلى هذه الن
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -173,7 +172,6 @@ https://opencode.ai/zen/v1/models
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ Našim modelima možete pristupiti i preko sljedećih API endpointa.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ Podržavamo pay-as-you-go model. Ispod su cijene **po 1M tokena**.
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ Du kan også få adgang til vores modeller gennem følgende API-endpoints.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ Vi understøtter en pay-as-you-go-model. Nedenfor er priserne **pr. 1M tokens**.
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -82,7 +82,6 @@ Du kannst auch über die folgenden API-Endpunkte auf unsere Modelle zugreifen.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -169,7 +168,6 @@ Wir unterstützen ein Pay-as-you-go-Modell. Unten findest du die Preise **pro 1M
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ También puedes acceder a nuestros modelos a través de los siguientes endpoints
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ Admitimos un modelo de pago por uso. A continuación se muestran los precios **p
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -82,7 +82,6 @@ Vous pouvez également accéder à nos modèles via les points de terminaison AP
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -169,7 +168,6 @@ Nous prenons en charge un modèle de paiement à l'utilisation. Vous trouverez c
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ Puoi anche accedere ai nostri modelli tramite i seguenti endpoint API.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ Supportiamo un modello pay-as-you-go. Qui sotto trovi i prezzi **per 1M token**.
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -82,7 +82,6 @@ OpenCode Zen は、OpenCode のほかのプロバイダーと同じように動
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -169,7 +168,6 @@ https://opencode.ai/zen/v1/models
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -82,7 +82,6 @@ OpenCode Zen은 OpenCode의 다른 provider와 똑같이 작동합니다.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -169,7 +168,6 @@ https://opencode.ai/zen/v1/models
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ Du kan også få tilgang til modellene våre gjennom følgende API-endepunkter.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ Vi støtter en pay-as-you-go-modell. Nedenfor er prisene **per 1M tokens**.
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ Możesz też uzyskać dostęp do naszych modeli przez poniższe endpointy API.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ Obsługujemy model pay-as-you-go. Poniżej znajdują się ceny **za 1M tokenów*
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -1718,10 +1718,6 @@ We recommend signing up for [ChatGPT Plus or Pro](https://chatgpt.com/pricing).
|
||||
/models
|
||||
```
|
||||
|
||||
##### Compute residency
|
||||
|
||||
For ChatGPT OAuth, OpenCode automatically applies a regional inference residency requirement when one is advertised by your workspace credentials. It forwards the compute residency value from the credential instead of maintaining a fixed list of regions. Data residency at rest does not imply regional inference.
|
||||
|
||||
##### Using API keys
|
||||
|
||||
If you already have an API key, you can select **Manually enter API Key** and paste it in your terminal.
|
||||
|
||||
@@ -82,7 +82,6 @@ Você também pode acessar nossos modelos pelos seguintes endpoints de API.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -169,7 +168,6 @@ Oferecemos um modelo pay-as-you-go. Abaixo estão os preços **por 1M tokens**.
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ OpenCode Zen работает как любой другой провайдер
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ https://opencode.ai/zen/v1/models
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -84,7 +84,6 @@ OpenCode Zen ทำงานเหมือน provider อื่น ๆ ใน
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -171,7 +170,6 @@ https://opencode.ai/zen/v1/models
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -82,7 +82,6 @@ Modellerimize aşağıdaki API uç noktaları aracılığıyla da erişebilirsin
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -169,7 +168,6 @@ Kullandıkça öde modelini destekliyoruz. Aşağıda **1M token başına** fiya
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -91,7 +91,6 @@ You can also access our models through the following API endpoints.
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -180,7 +179,6 @@ We support a pay-as-you-go model. Below are the prices **per 1M tokens**.
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -82,7 +82,6 @@ OpenCode Zen 的工作方式与 OpenCode 中的任何其他提供商相同。
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -169,7 +168,6 @@ https://opencode.ai/zen/v1/models
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
@@ -86,7 +86,6 @@ OpenCode Zen 的運作方式和 OpenCode 中的其他供應商一樣。
|
||||
| Claude Sonnet 4.5 | claude-sonnet-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Claude Haiku 4.5 | claude-haiku-4-5 | `https://opencode.ai/zen/v1/messages` | `@ai-sdk/anthropic` |
|
||||
| Gemini 3.6 Flash | gemini-3.6-flash | `https://opencode.ai/zen/v1/models/gemini-3.6-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.7 Flash | gemini-3.7-flash | `https://opencode.ai/zen/v1/models/gemini-3.7-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash | gemini-3.5-flash | `https://opencode.ai/zen/v1/models/gemini-3.5-flash` | `@ai-sdk/google` |
|
||||
| Gemini 3.5 Flash Lite | gemini-3.5-flash-lite | `https://opencode.ai/zen/v1/models/gemini-3.5-flash-lite` | `@ai-sdk/google` |
|
||||
| Gemini 3.1 Pro | gemini-3.1-pro | `https://opencode.ai/zen/v1/models/gemini-3.1-pro` | `@ai-sdk/google` |
|
||||
@@ -174,7 +173,6 @@ https://opencode.ai/zen/v1/models
|
||||
| Claude Sonnet 4.5 (> 200K tokens) | $6.00 | $22.50 | $0.60 | $7.50 |
|
||||
| Claude Haiku 4.5 | $1.00 | $5.00 | $0.10 | $1.25 |
|
||||
| Gemini 3.6 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.7 Flash | $1.50 | $7.50 | $0.15 | - |
|
||||
| Gemini 3.5 Flash | $1.50 | $9.00 | $0.15 | - |
|
||||
| Gemini 3.5 Flash Lite | $0.30 | $2.50 | $0.03 | - |
|
||||
| Gemini 3.1 Pro (≤ 200K tokens) | $2.00 | $12.00 | $0.20 | - |
|
||||
|
||||
Reference in New Issue
Block a user