|
|
@@ -1,6 +1,7 @@
|
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test"
|
|
|
import path from "path"
|
|
|
-import type { ModelMessage } from "ai"
|
|
|
+import { tool, type ModelMessage } from "ai"
|
|
|
+import z from "zod"
|
|
|
import { LLM } from "../../src/session/llm"
|
|
|
import { Global } from "../../src/global"
|
|
|
import { Instance } from "../../src/project/instance"
|
|
|
@@ -325,6 +326,95 @@ describe("session.llm.stream", () => {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+ test("keeps tools enabled by prompt permissions", async () => {
|
|
|
+ const server = state.server
|
|
|
+ if (!server) {
|
|
|
+ throw new Error("Server not initialized")
|
|
|
+ }
|
|
|
+
|
|
|
+ const providerID = "alibaba"
|
|
|
+ const modelID = "qwen-plus"
|
|
|
+ const fixture = await loadFixture(providerID, modelID)
|
|
|
+ const model = fixture.model
|
|
|
+
|
|
|
+ const request = waitRequest(
|
|
|
+ "/chat/completions",
|
|
|
+ new Response(createChatStream("Hello"), {
|
|
|
+ status: 200,
|
|
|
+ headers: { "Content-Type": "text/event-stream" },
|
|
|
+ }),
|
|
|
+ )
|
|
|
+
|
|
|
+ await using tmp = await tmpdir({
|
|
|
+ init: async (dir) => {
|
|
|
+ await Bun.write(
|
|
|
+ path.join(dir, "opencode.json"),
|
|
|
+ JSON.stringify({
|
|
|
+ $schema: "https://opencode.ai/config.json",
|
|
|
+ enabled_providers: [providerID],
|
|
|
+ provider: {
|
|
|
+ [providerID]: {
|
|
|
+ options: {
|
|
|
+ apiKey: "test-key",
|
|
|
+ baseURL: `${server.url.origin}/v1`,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ await Instance.provide({
|
|
|
+ directory: tmp.path,
|
|
|
+ fn: async () => {
|
|
|
+ const resolved = await Provider.getModel(providerID, model.id)
|
|
|
+ const sessionID = "session-test-tools"
|
|
|
+ const agent = {
|
|
|
+ name: "test",
|
|
|
+ mode: "primary",
|
|
|
+ options: {},
|
|
|
+ permission: [{ permission: "question", pattern: "*", action: "deny" }],
|
|
|
+ } satisfies Agent.Info
|
|
|
+
|
|
|
+ const user = {
|
|
|
+ id: "user-tools",
|
|
|
+ sessionID,
|
|
|
+ role: "user",
|
|
|
+ time: { created: Date.now() },
|
|
|
+ agent: agent.name,
|
|
|
+ model: { providerID, modelID: resolved.id },
|
|
|
+ tools: { question: true },
|
|
|
+ } satisfies MessageV2.User
|
|
|
+
|
|
|
+ const stream = await LLM.stream({
|
|
|
+ user,
|
|
|
+ sessionID,
|
|
|
+ model: resolved,
|
|
|
+ agent,
|
|
|
+ permission: [{ permission: "question", pattern: "*", action: "allow" }],
|
|
|
+ system: ["You are a helpful assistant."],
|
|
|
+ abort: new AbortController().signal,
|
|
|
+ messages: [{ role: "user", content: "Hello" }],
|
|
|
+ tools: {
|
|
|
+ question: tool({
|
|
|
+ description: "Ask a question",
|
|
|
+ inputSchema: z.object({}),
|
|
|
+ execute: async () => ({ output: "" }),
|
|
|
+ }),
|
|
|
+ },
|
|
|
+ })
|
|
|
+
|
|
|
+ for await (const _ of stream.fullStream) {
|
|
|
+ }
|
|
|
+
|
|
|
+ const capture = await request
|
|
|
+ const tools = capture.body.tools as Array<{ function?: { name?: string } }> | undefined
|
|
|
+ expect(tools?.some((item) => item.function?.name === "question")).toBe(true)
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
test("sends responses API payload for OpenAI models", async () => {
|
|
|
const server = state.server
|
|
|
if (!server) {
|