mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-02 08:25:00 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7902e04c3a |
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-AD5kt1o035hdvYPyrvjiz2mcsXFJ7UJVEmbE9AJFDFs=",
|
||||
"aarch64-linux": "sha256-f1gVXUpIRWC/I8dxIigdGGvPsju04MXVjZhtq5EYL64=",
|
||||
"aarch64-darwin": "sha256-c+JRpjs2gsgQ2czyM6bTbYEdMbX/erOSQdX6TdrI59k=",
|
||||
"x86_64-darwin": "sha256-iB7l4PtQQ9Mx/XAdtyayiLe6pZKhuXB9lpmrbTr+uWc="
|
||||
"x86_64-linux": "sha256-fK6zAHJC3ut/KUdfqLPxVMH7Z1yv65YZ7qcHF45kPas=",
|
||||
"aarch64-linux": "sha256-CSWZY/vVUCKzlwfisQQHqHjawUwL0BRc8Y9o2y/QZYA=",
|
||||
"aarch64-darwin": "sha256-IyFm5NbnU63BaOO/F4/v1exz3VbvkY96yjg9iun+O9Q=",
|
||||
"x86_64-darwin": "sha256-XnXFoUxqXmEx7AKx5YhaJuLY2NYMtI0ICafaenU5L6k="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,6 @@ import { httpClient } from "./effect/app-node-platform"
|
||||
export const CatalogModelStatus = Schema.Literals(["alpha", "beta", "deprecated"])
|
||||
export type CatalogModelStatus = typeof CatalogModelStatus.Type
|
||||
|
||||
const InterleavedField = Schema.Union([
|
||||
Schema.Literals(["reasoning", "reasoning_content", "reasoning_text"]),
|
||||
Schema.String,
|
||||
])
|
||||
|
||||
const USER_AGENT = `opencode/${InstallationChannel}/${InstallationVersion}/${Flag.OPENCODE_CLIENT}`
|
||||
|
||||
const CostTier = Schema.Struct({
|
||||
@@ -76,10 +71,9 @@ export const Model = Schema.Struct({
|
||||
reasoning_options: Schema.optional(Schema.Array(ReasoningOption)),
|
||||
interleaved: Schema.optional(
|
||||
Schema.Union([
|
||||
Schema.Boolean,
|
||||
InterleavedField,
|
||||
Schema.Literal(true),
|
||||
Schema.Struct({
|
||||
field: InterleavedField,
|
||||
field: Schema.Literals(["reasoning", "reasoning_content", "reasoning_details"]),
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
@@ -5,11 +5,6 @@ import { PositiveInt } from "../../schema"
|
||||
|
||||
export const ModelStatus = Schema.Literals(["alpha", "beta", "deprecated", "active"])
|
||||
|
||||
const InterleavedField = Schema.Union([
|
||||
Schema.Literals(["reasoning", "reasoning_content", "reasoning_text"]),
|
||||
Schema.String,
|
||||
])
|
||||
|
||||
export const Model = Schema.Struct({
|
||||
id: Schema.optional(Schema.String),
|
||||
name: Schema.optional(Schema.String),
|
||||
@@ -21,10 +16,9 @@ export const Model = Schema.Struct({
|
||||
tool_call: Schema.optional(Schema.Boolean),
|
||||
interleaved: Schema.optional(
|
||||
Schema.Union([
|
||||
Schema.Boolean,
|
||||
InterleavedField,
|
||||
Schema.Literal(true),
|
||||
Schema.Struct({
|
||||
field: InterleavedField,
|
||||
field: Schema.Literals(["reasoning", "reasoning_content", "reasoning_details"]),
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
@@ -123,18 +123,6 @@ async function toolError(part: ToolPart) {
|
||||
}
|
||||
}
|
||||
|
||||
async function embeddedServer(auth?: string) {
|
||||
const { Server } = await import("@/server/server")
|
||||
const server = Server.Default()
|
||||
const fetch = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const request = new Request(input, init)
|
||||
const headers = new Headers(request.headers)
|
||||
if (auth) headers.set("Authorization", auth)
|
||||
return server.app.fetch(new Request(request, { headers }))
|
||||
}) as typeof globalThis.fetch
|
||||
return { fetch, dispose: server.dispose }
|
||||
}
|
||||
|
||||
export const RunCommand = effectCmd({
|
||||
command: "run [message..]",
|
||||
describe: "run opencode with a message",
|
||||
@@ -914,12 +902,19 @@ export const RunCommand = effectCmd({
|
||||
if (interactive && !args.attach && !args.session && !args.continue) {
|
||||
const model = pick(args.model)
|
||||
const { runInteractiveLocalMode } = await import("./run/runtime")
|
||||
const server = await embeddedServer(ServerAuth.header())
|
||||
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const { Server } = await import("@/server/server")
|
||||
const request = new Request(input, init)
|
||||
const headers = new Headers(request.headers)
|
||||
const auth = ServerAuth.header()
|
||||
if (auth) headers.set("Authorization", auth)
|
||||
return Server.Default().app.fetch(new Request(request, { headers }))
|
||||
}) as typeof globalThis.fetch
|
||||
|
||||
try {
|
||||
return await runInteractiveLocalMode({
|
||||
directory: directory ?? root,
|
||||
fetch: server.fetch,
|
||||
fetch: fetchFn,
|
||||
resolveAgent: localAgent,
|
||||
session,
|
||||
share,
|
||||
@@ -937,8 +932,6 @@ export const RunCommand = effectCmd({
|
||||
})
|
||||
} catch (error) {
|
||||
dieInteractive(error)
|
||||
} finally {
|
||||
await server.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -947,17 +940,20 @@ export const RunCommand = effectCmd({
|
||||
return await execute(sdk)
|
||||
}
|
||||
|
||||
const server = await embeddedServer(ServerAuth.header())
|
||||
try {
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: "http://opencode.internal",
|
||||
fetch: server.fetch,
|
||||
directory,
|
||||
})
|
||||
await execute(sdk)
|
||||
} finally {
|
||||
await server.dispose()
|
||||
}
|
||||
const fetchFn = (async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const { Server } = await import("@/server/server")
|
||||
const request = new Request(input, init)
|
||||
const headers = new Headers(request.headers)
|
||||
const auth = ServerAuth.header()
|
||||
if (auth) headers.set("Authorization", auth)
|
||||
return Server.Default().app.fetch(new Request(request, { headers }))
|
||||
}) as typeof globalThis.fetch
|
||||
const sdk = createOpencodeClient({
|
||||
baseUrl: "http://opencode.internal",
|
||||
fetch: fetchFn,
|
||||
directory,
|
||||
})
|
||||
await execute(sdk)
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -72,7 +72,6 @@ export const rpc = {
|
||||
async shutdown() {
|
||||
await InstanceRuntime.disposeAllInstances()
|
||||
if (server) await server.stop(true)
|
||||
if (Server.Default.loaded()) await Server.Default().dispose()
|
||||
process.off("unhandledRejection", onUnhandledRejection)
|
||||
process.off("uncaughtException", onUncaughtException)
|
||||
},
|
||||
|
||||
@@ -976,15 +976,10 @@ const ProviderModalities = Schema.Struct({
|
||||
pdf: Schema.Boolean,
|
||||
})
|
||||
|
||||
const ProviderInterleavedField = Schema.Union([
|
||||
Schema.Literals(["reasoning", "reasoning_content", "reasoning_text"]),
|
||||
Schema.String,
|
||||
])
|
||||
|
||||
const ProviderInterleaved = Schema.Union([
|
||||
Schema.Boolean,
|
||||
Schema.Struct({
|
||||
field: ProviderInterleavedField,
|
||||
field: Schema.Literals(["reasoning", "reasoning_content", "reasoning_details"]),
|
||||
}),
|
||||
])
|
||||
|
||||
@@ -1248,7 +1243,7 @@ function fromModelsDevModel(provider: ModelsDev.Provider, model: ModelsDev.Model
|
||||
video: model.modalities?.output?.includes("video") ?? false,
|
||||
pdf: model.modalities?.output?.includes("pdf") ?? false,
|
||||
},
|
||||
interleaved: typeof model.interleaved === "string" ? { field: model.interleaved } : (model.interleaved ?? false),
|
||||
interleaved: model.interleaved ?? false,
|
||||
},
|
||||
release_date: model.release_date ?? "",
|
||||
variants: {},
|
||||
@@ -1480,7 +1475,7 @@ const layer = Layer.effect(
|
||||
pdf: model.modalities?.output?.includes("pdf") ?? existingModel?.capabilities.output.pdf ?? false,
|
||||
},
|
||||
interleaved:
|
||||
(typeof model.interleaved === "string" ? { field: model.interleaved } : model.interleaved) ??
|
||||
model.interleaved ??
|
||||
existingModel?.capabilities.interleaved ??
|
||||
(!existingModel && apiNpm === "@ai-sdk/openai-compatible" && apiID.includes("deepseek")
|
||||
? { field: "reasoning_content" }
|
||||
|
||||
@@ -54,14 +54,14 @@ class ListenerServerService extends Context.Service<ListenerServerService, Liste
|
||||
) {}
|
||||
|
||||
export const Default = lazy(() => {
|
||||
const web = HttpApiApp.webHandler()
|
||||
const handler = HttpApiApp.webHandler().handler
|
||||
const app: ServerApp = {
|
||||
fetch: (request: Request) => web.handler(request, HttpApiApp.context),
|
||||
fetch: (request: Request) => handler(request, HttpApiApp.context),
|
||||
request(input, init) {
|
||||
return app.fetch(input instanceof Request ? input : new Request(new URL(input, "http://localhost"), init))
|
||||
},
|
||||
}
|
||||
return { app, dispose: web.dispose }
|
||||
return { app }
|
||||
})
|
||||
|
||||
export async function openapi() {
|
||||
|
||||
@@ -23,35 +23,6 @@ describe("opencode run (non-interactive subprocess)", () => {
|
||||
60_000,
|
||||
)
|
||||
|
||||
cliIt.concurrent(
|
||||
"flushes server telemetry before exiting",
|
||||
({ llm, opencode }) =>
|
||||
Effect.gen(function* () {
|
||||
const requests: string[] = []
|
||||
const collector = yield* Effect.acquireRelease(
|
||||
Effect.sync(() =>
|
||||
Bun.serve({
|
||||
port: 0,
|
||||
fetch(request) {
|
||||
requests.push(new URL(request.url).pathname)
|
||||
return new Response(null, { status: 200 })
|
||||
},
|
||||
}),
|
||||
),
|
||||
(server) => Effect.sync(() => server.stop(true)),
|
||||
)
|
||||
yield* llm.text("telemetry response")
|
||||
|
||||
const result = yield* opencode.run("say hi", {
|
||||
env: { OTEL_EXPORTER_OTLP_ENDPOINT: collector.url.toString().replace(/\/$/, "") },
|
||||
})
|
||||
|
||||
opencode.expectExit(result, 0)
|
||||
expect(requests).toContain("/v1/traces")
|
||||
}),
|
||||
60_000,
|
||||
)
|
||||
|
||||
cliIt.concurrent(
|
||||
"prints each completed text part in order around a tool continuation",
|
||||
({ llm, opencode }) =>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { expect, test } from "bun:test"
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||
|
||||
test("does not reconnect an SSE stream after a JSON-RPC error response", async () => {
|
||||
let requests = 0
|
||||
const transport = new StreamableHTTPClientTransport(new URL("http://mcp.invalid"), {
|
||||
fetch: async () => {
|
||||
requests += 1
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode("id: prime\nretry: 1\ndata:\n\n"))
|
||||
controller.enqueue(
|
||||
new TextEncoder().encode(
|
||||
'id: error\ndata: {"jsonrpc":"2.0","error":{"code":-32601,"message":"Method not found"},"id":1}\n\n',
|
||||
),
|
||||
)
|
||||
controller.close()
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "text/event-stream" } },
|
||||
)
|
||||
},
|
||||
reconnectionOptions: {
|
||||
initialReconnectionDelay: 1,
|
||||
maxReconnectionDelay: 1,
|
||||
reconnectionDelayGrowFactor: 1,
|
||||
maxRetries: 2,
|
||||
},
|
||||
})
|
||||
|
||||
await transport.start()
|
||||
await transport.send({ jsonrpc: "2.0", method: "resources/list", id: 1 })
|
||||
await Bun.sleep(25)
|
||||
await transport.close()
|
||||
|
||||
expect(requests).toBe(1)
|
||||
})
|
||||
@@ -252,8 +252,6 @@ it.instance(
|
||||
const provider = providers[ProviderV2.ID.make("custom-provider")]
|
||||
expect(provider.models["deepseek-r1"].capabilities.interleaved).toEqual({ field: "reasoning_content" })
|
||||
expect(provider.models["deepseek-details"].capabilities.interleaved).toEqual({ field: "reasoning_details" })
|
||||
expect(provider.models["deepseek-text"].capabilities.interleaved).toEqual({ field: "reasoning_text" })
|
||||
expect(provider.models["custom-reasoning"].capabilities.interleaved).toEqual({ field: "vendor_reasoning" })
|
||||
expect(provider.models["custom-model"].capabilities.interleaved).toBe(false)
|
||||
expect(
|
||||
providers[ProviderV2.ID.make("custom-anthropic-provider")].models["deepseek-r1"].capabilities.interleaved,
|
||||
@@ -269,8 +267,6 @@ it.instance(
|
||||
models: {
|
||||
"deepseek-r1": { name: "DeepSeek R1" },
|
||||
"deepseek-details": { name: "DeepSeek Details", interleaved: { field: "reasoning_details" } },
|
||||
"deepseek-text": { name: "DeepSeek Text", interleaved: "reasoning_text" },
|
||||
"custom-reasoning": { name: "Custom Reasoning", interleaved: { field: "vendor_reasoning" } },
|
||||
"custom-model": { name: "Custom Model" },
|
||||
},
|
||||
options: { apiKey: "custom-key" },
|
||||
@@ -1466,7 +1462,6 @@ test("models.dev normalization fills required response fields", () => {
|
||||
id: "gpt-5.4",
|
||||
name: "GPT-5.4",
|
||||
family: "gpt",
|
||||
interleaved: "reasoning_text",
|
||||
cost: { input: 2.5, output: 15 },
|
||||
limit: { context: 1_050_000, input: 922_000, output: 128_000 },
|
||||
},
|
||||
@@ -1479,7 +1474,6 @@ test("models.dev normalization fills required response fields", () => {
|
||||
expect(model.capabilities.reasoning).toBe(false)
|
||||
expect(model.capabilities.attachment).toBe(false)
|
||||
expect(model.capabilities.toolcall).toBe(true)
|
||||
expect(model.capabilities.interleaved).toEqual({ field: "reasoning_text" })
|
||||
expect(model.release_date).toBe("")
|
||||
})
|
||||
|
||||
|
||||
@@ -1768,13 +1768,9 @@ export type ProviderConfig = {
|
||||
temperature?: boolean
|
||||
tool_call?: boolean
|
||||
interleaved?:
|
||||
| boolean
|
||||
| "reasoning"
|
||||
| "reasoning_content"
|
||||
| "reasoning_text"
|
||||
| string
|
||||
| true
|
||||
| {
|
||||
field: "reasoning" | "reasoning_content" | "reasoning_text" | string
|
||||
field: "reasoning" | "reasoning_content" | "reasoning_details"
|
||||
}
|
||||
cost?: {
|
||||
input: number
|
||||
@@ -2061,7 +2057,7 @@ export type Model = {
|
||||
interleaved:
|
||||
| boolean
|
||||
| {
|
||||
field: "reasoning" | "reasoning_content" | "reasoning_text" | string
|
||||
field: "reasoning" | "reasoning_content" | "reasoning_details"
|
||||
}
|
||||
}
|
||||
cost: {
|
||||
|
||||
@@ -20820,28 +20820,15 @@
|
||||
"interleaved": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["reasoning", "reasoning_content", "reasoning_text"]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
"type": "boolean",
|
||||
"enum": [true]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["reasoning", "reasoning_content", "reasoning_text"]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
"type": "string",
|
||||
"enum": ["reasoning", "reasoning_content", "reasoning_details"]
|
||||
}
|
||||
},
|
||||
"required": ["field"],
|
||||
@@ -21654,15 +21641,8 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["reasoning", "reasoning_content", "reasoning_text"]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
"type": "string",
|
||||
"enum": ["reasoning", "reasoning_content", "reasoning_details"]
|
||||
}
|
||||
},
|
||||
"required": ["field"],
|
||||
|
||||
@@ -1505,30 +1505,6 @@ To use Kimi K2 from Moonshot AI:
|
||||
|
||||
---
|
||||
|
||||
### Modal
|
||||
|
||||
1. Create a [shared Endpoint](https://modal.com/endpoints) for the model you want to use.
|
||||
|
||||
2. Create a [proxy token](https://modal.com/docs/guide/endpoints#proxy-tokens), then join its ID and secret with a period:
|
||||
|
||||
```txt
|
||||
wk-<id>.ws-<secret>
|
||||
```
|
||||
|
||||
3. Run the `/connect` command, search for **Modal**, and enter the combined proxy token.
|
||||
|
||||
```txt
|
||||
/connect
|
||||
```
|
||||
|
||||
4. Run the `/models` command to select one of the endpoints in your Modal workspace.
|
||||
|
||||
```txt
|
||||
/models
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### NVIDIA
|
||||
|
||||
NVIDIA provides access to Nemotron models and many other open models through [build.nvidia.com](https://build.nvidia.com) for free.
|
||||
|
||||
@@ -141,15 +141,6 @@ diff --git a/dist/cjs/client/streamableHttp.js b/dist/cjs/client/streamableHttp.
|
||||
index a29a7d3a0f14d9cd800ef5b296485237350c666f..c362ae5fe6c62c8c8eae7e2e61de1eedff5443c9 100644
|
||||
--- a/dist/cjs/client/streamableHttp.js
|
||||
+++ b/dist/cjs/client/streamableHttp.js
|
||||
@@ -204,7 +204,7 @@ class StreamableHTTPClientTransport {
|
||||
if (!event.event || event.event === 'message') {
|
||||
try {
|
||||
const message = types_js_1.JSONRPCMessageSchema.parse(JSON.parse(event.data));
|
||||
- if ((0, types_js_1.isJSONRPCResultResponse)(message)) {
|
||||
+ if ((0, types_js_1.isJSONRPCResultResponse)(message) || (0, types_js_1.isJSONRPCErrorResponse)(message)) {
|
||||
// Mark that we received a response - no need to reconnect for this request
|
||||
receivedResponse = true;
|
||||
if (replayMessageId !== undefined) {
|
||||
@@ -290,7 +290,38 @@ class StreamableHTTPClientTransport {
|
||||
this.onclose?.();
|
||||
}
|
||||
@@ -527,19 +518,10 @@ index 624172aa24ae255a67c083f9c19053343e4a0581..ac75b14545fda44aff7ff4d97cc5da88
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createFetchWithInit, normalizeHeaders } from '../shared/transport.js';
|
||||
-import { isInitializedNotification, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
|
||||
+import { isInitializedNotification, isInitializeRequest, isJSONRPCErrorResponse, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
|
||||
+import { isInitializedNotification, isInitializeRequest, isJSONRPCRequest, isJSONRPCResultResponse, JSONRPCMessageSchema } from '../types.js';
|
||||
import { auth, extractWWWAuthenticateParams, UnauthorizedError } from './auth.js';
|
||||
import { EventSourceParserStream } from 'eventsource-parser/stream';
|
||||
// Default reconnection options for StreamableHTTP connections
|
||||
@@ -200,7 +200,7 @@ export class StreamableHTTPClientTransport {
|
||||
if (!event.event || event.event === 'message') {
|
||||
try {
|
||||
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));
|
||||
- if (isJSONRPCResultResponse(message)) {
|
||||
+ if (isJSONRPCResultResponse(message) || isJSONRPCErrorResponse(message)) {
|
||||
// Mark that we received a response - no need to reconnect for this request
|
||||
receivedResponse = true;
|
||||
if (replayMessageId !== undefined) {
|
||||
@@ -286,7 +286,38 @@ export class StreamableHTTPClientTransport {
|
||||
this.onclose?.();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user