Compare commits
6
Commits
v1.2.6
...
test-fields
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b01ed950de | ||
|
|
d93cefd47a | ||
|
|
a580fb47d2 | ||
|
|
9d3c81a683 | ||
|
|
86e545a23e | ||
|
|
b0afdf6ea4 |
@@ -359,6 +359,7 @@ opencode serve --hostname 0.0.0.0 --port 4096
|
|||||||
opencode serve [--port <number>] [--hostname <string>] [--cors <origin>]
|
opencode serve [--port <number>] [--hostname <string>] [--cors <origin>]
|
||||||
opencode session [command]
|
opencode session [command]
|
||||||
opencode session list
|
opencode session list
|
||||||
|
opencode session delete <sessionID>
|
||||||
opencode stats
|
opencode stats
|
||||||
opencode uninstall
|
opencode uninstall
|
||||||
opencode upgrade
|
opencode upgrade
|
||||||
@@ -598,6 +599,7 @@ OPENCODE_EXPERIMENTAL_MARKDOWN
|
|||||||
OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX
|
OPENCODE_EXPERIMENTAL_OUTPUT_TOKEN_MAX
|
||||||
OPENCODE_EXPERIMENTAL_OXFMT
|
OPENCODE_EXPERIMENTAL_OXFMT
|
||||||
OPENCODE_EXPERIMENTAL_PLAN_MODE
|
OPENCODE_EXPERIMENTAL_PLAN_MODE
|
||||||
|
OPENCODE_ENABLE_QUESTION_TOOL
|
||||||
OPENCODE_FAKE_VCS
|
OPENCODE_FAKE_VCS
|
||||||
OPENCODE_GIT_BASH_PATH
|
OPENCODE_GIT_BASH_PATH
|
||||||
OPENCODE_MODEL
|
OPENCODE_MODEL
|
||||||
|
|||||||
@@ -87,6 +87,8 @@
|
|||||||
"url": "https://github.com/anomalyco/opencode"
|
"url": "https://github.com/anomalyco/opencode"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"randomField": "hello-world-12345",
|
||||||
|
"anotherRandomField": "xyz-abc-789",
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"printWidth": 120
|
"printWidth": 120
|
||||||
|
|||||||
@@ -174,21 +174,6 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input:-webkit-autofill,
|
|
||||||
input:-webkit-autofill:hover,
|
|
||||||
input:-webkit-autofill:focus,
|
|
||||||
input:-webkit-autofill:active {
|
|
||||||
transition: background-color 5000000s ease-in-out 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:-webkit-autofill {
|
|
||||||
-webkit-text-fill-color: var(--color-text-strong) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:-moz-autofill {
|
|
||||||
-moz-text-fill-color: var(--color-text-strong) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-component="container"] {
|
[data-component="container"] {
|
||||||
max-width: 67.5rem;
|
max-width: 67.5rem;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -1249,4 +1234,19 @@ body {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active {
|
||||||
|
transition: background-color 5000000s ease-in-out 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:-webkit-autofill {
|
||||||
|
-webkit-text-fill-color: var(--color-text-strong) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:-moz-autofill {
|
||||||
|
-moz-text-fill-color: var(--color-text-strong) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ opencode acp
|
|||||||
opencode acp --cwd /path/to/project
|
opencode acp --cwd /path/to/project
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Question Tool Opt-In
|
||||||
|
|
||||||
|
ACP excludes `QuestionTool` by default.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
OPENCODE_ENABLE_QUESTION_TOOL=1 opencode acp
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable this only for ACP clients that support interactive question prompts.
|
||||||
|
|
||||||
### Programmatic
|
### Programmatic
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export class ACPSessionManager {
|
|||||||
const session = await this.sdk.session
|
const session = await this.sdk.session
|
||||||
.create(
|
.create(
|
||||||
{
|
{
|
||||||
title: `ACP Session ${crypto.randomUUID()}`,
|
|
||||||
directory: cwd,
|
directory: cwd,
|
||||||
},
|
},
|
||||||
{ throwOnError: true },
|
{ throwOnError: true },
|
||||||
|
|||||||
@@ -38,10 +38,34 @@ function pagerCmd(): string[] {
|
|||||||
export const SessionCommand = cmd({
|
export const SessionCommand = cmd({
|
||||||
command: "session",
|
command: "session",
|
||||||
describe: "manage sessions",
|
describe: "manage sessions",
|
||||||
builder: (yargs: Argv) => yargs.command(SessionListCommand).demandCommand(),
|
builder: (yargs: Argv) => yargs.command(SessionListCommand).command(SessionDeleteCommand).demandCommand(),
|
||||||
async handler() {},
|
async handler() {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const SessionDeleteCommand = cmd({
|
||||||
|
command: "delete <sessionID>",
|
||||||
|
describe: "delete a session",
|
||||||
|
builder: (yargs: Argv) => {
|
||||||
|
return yargs.positional("sessionID", {
|
||||||
|
describe: "session ID to delete",
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handler: async (args) => {
|
||||||
|
await bootstrap(process.cwd(), async () => {
|
||||||
|
try {
|
||||||
|
await Session.get(args.sessionID)
|
||||||
|
} catch {
|
||||||
|
UI.error(`Session not found: ${args.sessionID}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
await Session.remove(args.sessionID)
|
||||||
|
UI.println(UI.Style.TEXT_SUCCESS_BOLD + `Session ${args.sessionID} deleted` + UI.Style.TEXT_NORMAL)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const SessionListCommand = cmd({
|
export const SessionListCommand = cmd({
|
||||||
command: "list",
|
command: "list",
|
||||||
describe: "list sessions",
|
describe: "list sessions",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export namespace Flag {
|
|||||||
export declare const OPENCODE_CLIENT: string
|
export declare const OPENCODE_CLIENT: string
|
||||||
export const OPENCODE_SERVER_PASSWORD = process.env["OPENCODE_SERVER_PASSWORD"]
|
export const OPENCODE_SERVER_PASSWORD = process.env["OPENCODE_SERVER_PASSWORD"]
|
||||||
export const OPENCODE_SERVER_USERNAME = process.env["OPENCODE_SERVER_USERNAME"]
|
export const OPENCODE_SERVER_USERNAME = process.env["OPENCODE_SERVER_USERNAME"]
|
||||||
|
export const OPENCODE_ENABLE_QUESTION_TOOL = truthy("OPENCODE_ENABLE_QUESTION_TOOL")
|
||||||
|
|
||||||
// Experimental
|
// Experimental
|
||||||
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
|
export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
|
||||||
|
|||||||
@@ -445,6 +445,12 @@ export namespace SessionPrompt {
|
|||||||
log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
|
log.error("subtask execution failed", { error, agent: task.agent, description: task.description })
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
const attachments = result?.attachments?.map((attachment) => ({
|
||||||
|
...attachment,
|
||||||
|
id: Identifier.ascending("part"),
|
||||||
|
sessionID,
|
||||||
|
messageID: assistantMessage.id,
|
||||||
|
}))
|
||||||
await Plugin.trigger(
|
await Plugin.trigger(
|
||||||
"tool.execute.after",
|
"tool.execute.after",
|
||||||
{
|
{
|
||||||
@@ -467,7 +473,7 @@ export namespace SessionPrompt {
|
|||||||
title: result.title,
|
title: result.title,
|
||||||
metadata: result.metadata,
|
metadata: result.metadata,
|
||||||
output: result.output,
|
output: result.output,
|
||||||
attachments: result.attachments,
|
attachments,
|
||||||
time: {
|
time: {
|
||||||
...part.state.time,
|
...part.state.time,
|
||||||
end: Date.now(),
|
end: Date.now(),
|
||||||
@@ -797,6 +803,15 @@ export namespace SessionPrompt {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
const result = await item.execute(args, ctx)
|
const result = await item.execute(args, ctx)
|
||||||
|
const output = {
|
||||||
|
...result,
|
||||||
|
attachments: result.attachments?.map((attachment) => ({
|
||||||
|
...attachment,
|
||||||
|
id: Identifier.ascending("part"),
|
||||||
|
sessionID: ctx.sessionID,
|
||||||
|
messageID: input.processor.message.id,
|
||||||
|
})),
|
||||||
|
}
|
||||||
await Plugin.trigger(
|
await Plugin.trigger(
|
||||||
"tool.execute.after",
|
"tool.execute.after",
|
||||||
{
|
{
|
||||||
@@ -805,9 +820,9 @@ export namespace SessionPrompt {
|
|||||||
callID: ctx.callID,
|
callID: ctx.callID,
|
||||||
args,
|
args,
|
||||||
},
|
},
|
||||||
result,
|
output,
|
||||||
)
|
)
|
||||||
return result
|
return output
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -855,16 +870,13 @@ export namespace SessionPrompt {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const textParts: string[] = []
|
const textParts: string[] = []
|
||||||
const attachments: MessageV2.FilePart[] = []
|
const attachments: Omit<MessageV2.FilePart, "id" | "sessionID" | "messageID">[] = []
|
||||||
|
|
||||||
for (const contentItem of result.content) {
|
for (const contentItem of result.content) {
|
||||||
if (contentItem.type === "text") {
|
if (contentItem.type === "text") {
|
||||||
textParts.push(contentItem.text)
|
textParts.push(contentItem.text)
|
||||||
} else if (contentItem.type === "image") {
|
} else if (contentItem.type === "image") {
|
||||||
attachments.push({
|
attachments.push({
|
||||||
id: Identifier.ascending("part"),
|
|
||||||
sessionID: input.session.id,
|
|
||||||
messageID: input.processor.message.id,
|
|
||||||
type: "file",
|
type: "file",
|
||||||
mime: contentItem.mimeType,
|
mime: contentItem.mimeType,
|
||||||
url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
|
url: `data:${contentItem.mimeType};base64,${contentItem.data}`,
|
||||||
@@ -876,9 +888,6 @@ export namespace SessionPrompt {
|
|||||||
}
|
}
|
||||||
if (resource.blob) {
|
if (resource.blob) {
|
||||||
attachments.push({
|
attachments.push({
|
||||||
id: Identifier.ascending("part"),
|
|
||||||
sessionID: input.session.id,
|
|
||||||
messageID: input.processor.message.id,
|
|
||||||
type: "file",
|
type: "file",
|
||||||
mime: resource.mimeType ?? "application/octet-stream",
|
mime: resource.mimeType ?? "application/octet-stream",
|
||||||
url: `data:${resource.mimeType ?? "application/octet-stream"};base64,${resource.blob}`,
|
url: `data:${resource.mimeType ?? "application/octet-stream"};base64,${resource.blob}`,
|
||||||
@@ -1157,6 +1166,7 @@ export namespace SessionPrompt {
|
|||||||
pieces.push(
|
pieces.push(
|
||||||
...result.attachments.map((attachment) => ({
|
...result.attachments.map((attachment) => ({
|
||||||
...attachment,
|
...attachment,
|
||||||
|
id: Identifier.ascending("part"),
|
||||||
synthetic: true,
|
synthetic: true,
|
||||||
filename: attachment.filename ?? part.filename,
|
filename: attachment.filename ?? part.filename,
|
||||||
messageID: info.id,
|
messageID: info.id,
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ export const BatchTool = Tool.define("batch", async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const result = await tool.execute(validatedParams, { ...ctx, callID: partID })
|
const result = await tool.execute(validatedParams, { ...ctx, callID: partID })
|
||||||
|
const attachments = result.attachments?.map((attachment) => ({
|
||||||
|
...attachment,
|
||||||
|
id: Identifier.ascending("part"),
|
||||||
|
sessionID: ctx.sessionID,
|
||||||
|
messageID: ctx.messageID,
|
||||||
|
}))
|
||||||
|
|
||||||
await Session.updatePart({
|
await Session.updatePart({
|
||||||
id: partID,
|
id: partID,
|
||||||
@@ -91,7 +97,7 @@ export const BatchTool = Tool.define("batch", async () => {
|
|||||||
output: result.output,
|
output: result.output,
|
||||||
title: result.title,
|
title: result.title,
|
||||||
metadata: result.metadata,
|
metadata: result.metadata,
|
||||||
attachments: result.attachments,
|
attachments,
|
||||||
time: {
|
time: {
|
||||||
start: callStartTime,
|
start: callStartTime,
|
||||||
end: Date.now(),
|
end: Date.now(),
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { LSP } from "../lsp"
|
|||||||
import { FileTime } from "../file/time"
|
import { FileTime } from "../file/time"
|
||||||
import DESCRIPTION from "./read.txt"
|
import DESCRIPTION from "./read.txt"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { Identifier } from "../id/id"
|
|
||||||
import { assertExternalDirectory } from "./external-directory"
|
import { assertExternalDirectory } from "./external-directory"
|
||||||
import { InstructionPrompt } from "../session/instruction"
|
import { InstructionPrompt } from "../session/instruction"
|
||||||
|
|
||||||
@@ -127,9 +126,6 @@ export const ReadTool = Tool.define("read", {
|
|||||||
},
|
},
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
id: Identifier.ascending("part"),
|
|
||||||
sessionID: ctx.sessionID,
|
|
||||||
messageID: ctx.messageID,
|
|
||||||
type: "file",
|
type: "file",
|
||||||
mime,
|
mime,
|
||||||
url: `data:${mime};base64,${Buffer.from(await file.bytes()).toString("base64")}`,
|
url: `data:${mime};base64,${Buffer.from(await file.bytes()).toString("base64")}`,
|
||||||
|
|||||||
@@ -94,10 +94,11 @@ export namespace ToolRegistry {
|
|||||||
async function all(): Promise<Tool.Info[]> {
|
async function all(): Promise<Tool.Info[]> {
|
||||||
const custom = await state().then((x) => x.custom)
|
const custom = await state().then((x) => x.custom)
|
||||||
const config = await Config.get()
|
const config = await Config.get()
|
||||||
|
const question = ["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) || Flag.OPENCODE_ENABLE_QUESTION_TOOL
|
||||||
|
|
||||||
return [
|
return [
|
||||||
InvalidTool,
|
InvalidTool,
|
||||||
...(["app", "cli", "desktop"].includes(Flag.OPENCODE_CLIENT) ? [QuestionTool] : []),
|
...(question ? [QuestionTool] : []),
|
||||||
BashTool,
|
BashTool,
|
||||||
ReadTool,
|
ReadTool,
|
||||||
GlobTool,
|
GlobTool,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export namespace Tool {
|
|||||||
title: string
|
title: string
|
||||||
metadata: M
|
metadata: M
|
||||||
output: string
|
output: string
|
||||||
attachments?: MessageV2.FilePart[]
|
attachments?: Omit<MessageV2.FilePart, "id" | "sessionID" | "messageID">[]
|
||||||
}>
|
}>
|
||||||
formatValidationError?(error: z.ZodError): string
|
formatValidationError?(error: z.ZodError): string
|
||||||
}>
|
}>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Tool } from "./tool"
|
|||||||
import TurndownService from "turndown"
|
import TurndownService from "turndown"
|
||||||
import DESCRIPTION from "./webfetch.txt"
|
import DESCRIPTION from "./webfetch.txt"
|
||||||
import { abortAfterAny } from "../util/abort"
|
import { abortAfterAny } from "../util/abort"
|
||||||
import { Identifier } from "../id/id"
|
|
||||||
|
|
||||||
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB
|
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB
|
||||||
const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds
|
const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds
|
||||||
@@ -103,9 +102,6 @@ export const WebFetchTool = Tool.define("webfetch", {
|
|||||||
metadata: {},
|
metadata: {},
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
id: Identifier.ascending("part"),
|
|
||||||
sessionID: ctx.sessionID,
|
|
||||||
messageID: ctx.messageID,
|
|
||||||
type: "file",
|
type: "file",
|
||||||
mime,
|
mime,
|
||||||
url: `data:${mime};base64,${base64Content}`,
|
url: `data:${mime};base64,${base64Content}`,
|
||||||
|
|||||||
@@ -349,6 +349,9 @@ describe("tool.read truncation", () => {
|
|||||||
expect(result.metadata.truncated).toBe(false)
|
expect(result.metadata.truncated).toBe(false)
|
||||||
expect(result.attachments).toBeDefined()
|
expect(result.attachments).toBeDefined()
|
||||||
expect(result.attachments?.length).toBe(1)
|
expect(result.attachments?.length).toBe(1)
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("id")
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("sessionID")
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("messageID")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -363,6 +366,9 @@ describe("tool.read truncation", () => {
|
|||||||
expect(result.attachments).toBeDefined()
|
expect(result.attachments).toBeDefined()
|
||||||
expect(result.attachments?.length).toBe(1)
|
expect(result.attachments?.length).toBe(1)
|
||||||
expect(result.attachments?.[0].type).toBe("file")
|
expect(result.attachments?.[0].type).toBe("file")
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("id")
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("sessionID")
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("messageID")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ describe("tool.webfetch", () => {
|
|||||||
expect(result.attachments?.[0].type).toBe("file")
|
expect(result.attachments?.[0].type).toBe("file")
|
||||||
expect(result.attachments?.[0].mime).toBe("image/png")
|
expect(result.attachments?.[0].mime).toBe("image/png")
|
||||||
expect(result.attachments?.[0].url.startsWith("data:image/png;base64,")).toBe(true)
|
expect(result.attachments?.[0].url.startsWith("data:image/png;base64,")).toBe(true)
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("id")
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("sessionID")
|
||||||
|
expect(result.attachments?.[0]).not.toHaveProperty("messageID")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user