mirror of
https://github.com/aljazceru/opencode.git
synced 2025-12-20 09:14:22 +01:00
wip: tui permissions
This commit is contained in:
@@ -2,6 +2,7 @@ import { App } from "../app/app"
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
import { Installation } from "../installation"
|
||||||
|
|
||||||
export namespace Permission {
|
export namespace Permission {
|
||||||
const log = Log.create({ service: "permission" })
|
const log = Log.create({ service: "permission" })
|
||||||
@@ -10,6 +11,8 @@ export namespace Permission {
|
|||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
sessionID: z.string(),
|
sessionID: z.string(),
|
||||||
|
messageID: z.string(),
|
||||||
|
toolCallID: z.string().optional(),
|
||||||
title: z.string(),
|
title: z.string(),
|
||||||
metadata: z.record(z.any()),
|
metadata: z.record(z.any()),
|
||||||
time: z.object({
|
time: z.object({
|
||||||
@@ -17,7 +20,7 @@ export namespace Permission {
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.openapi({
|
.openapi({
|
||||||
ref: "permission.info",
|
ref: "Permission",
|
||||||
})
|
})
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
@@ -52,7 +55,7 @@ export namespace Permission {
|
|||||||
async (state) => {
|
async (state) => {
|
||||||
for (const pending of Object.values(state.pending)) {
|
for (const pending of Object.values(state.pending)) {
|
||||||
for (const item of Object.values(pending)) {
|
for (const item of Object.values(pending)) {
|
||||||
item.reject(new RejectedError(item.info.sessionID, item.info.id))
|
item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.toolCallID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -61,25 +64,35 @@ export namespace Permission {
|
|||||||
export function ask(input: {
|
export function ask(input: {
|
||||||
id: Info["id"]
|
id: Info["id"]
|
||||||
sessionID: Info["sessionID"]
|
sessionID: Info["sessionID"]
|
||||||
|
messageID: Info["messageID"]
|
||||||
|
toolCallID?: Info["toolCallID"]
|
||||||
title: Info["title"]
|
title: Info["title"]
|
||||||
metadata: Info["metadata"]
|
metadata: Info["metadata"]
|
||||||
}) {
|
}) {
|
||||||
return
|
// TODO: dax, remove this when you're happy with permissions
|
||||||
|
if (!Installation.isDev()) return
|
||||||
|
|
||||||
const { pending, approved } = state()
|
const { pending, approved } = state()
|
||||||
log.info("asking", {
|
log.info("asking", {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
permissionID: input.id,
|
permissionID: input.id,
|
||||||
|
messageID: input.messageID,
|
||||||
|
toolCallID: input.toolCallID,
|
||||||
})
|
})
|
||||||
if (approved[input.sessionID]?.[input.id]) {
|
if (approved[input.sessionID]?.[input.id]) {
|
||||||
log.info("previously approved", {
|
log.info("previously approved", {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
permissionID: input.id,
|
permissionID: input.id,
|
||||||
|
messageID: input.messageID,
|
||||||
|
toolCallID: input.toolCallID,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const info: Info = {
|
const info: Info = {
|
||||||
id: input.id,
|
id: input.id,
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
|
messageID: input.messageID,
|
||||||
|
toolCallID: input.toolCallID,
|
||||||
title: input.title,
|
title: input.title,
|
||||||
metadata: input.metadata,
|
metadata: input.metadata,
|
||||||
time: {
|
time: {
|
||||||
@@ -93,29 +106,28 @@ export namespace Permission {
|
|||||||
resolve,
|
resolve,
|
||||||
reject,
|
reject,
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
respond({
|
// respond({
|
||||||
sessionID: input.sessionID,
|
// sessionID: input.sessionID,
|
||||||
permissionID: input.id,
|
// permissionID: input.id,
|
||||||
response: "always",
|
// response: "always",
|
||||||
})
|
// })
|
||||||
}, 1000)
|
// }, 1000)
|
||||||
Bus.publish(Event.Updated, info)
|
Bus.publish(Event.Updated, info)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function respond(input: {
|
export const Response = z.enum(["once", "always", "reject"])
|
||||||
sessionID: Info["sessionID"]
|
export type Response = z.infer<typeof Response>
|
||||||
permissionID: Info["id"]
|
|
||||||
response: "once" | "always" | "reject"
|
export function respond(input: { sessionID: Info["sessionID"]; permissionID: Info["id"]; response: Response }) {
|
||||||
}) {
|
|
||||||
log.info("response", input)
|
log.info("response", input)
|
||||||
const { pending, approved } = state()
|
const { pending, approved } = state()
|
||||||
const match = pending[input.sessionID]?.[input.permissionID]
|
const match = pending[input.sessionID]?.[input.permissionID]
|
||||||
if (!match) return
|
if (!match) return
|
||||||
delete pending[input.sessionID][input.permissionID]
|
delete pending[input.sessionID][input.permissionID]
|
||||||
if (input.response === "reject") {
|
if (input.response === "reject") {
|
||||||
match.reject(new RejectedError(input.sessionID, input.permissionID))
|
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.toolCallID))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
match.resolve()
|
match.resolve()
|
||||||
@@ -129,6 +141,7 @@ export namespace Permission {
|
|||||||
constructor(
|
constructor(
|
||||||
public readonly sessionID: string,
|
public readonly sessionID: string,
|
||||||
public readonly permissionID: string,
|
public readonly permissionID: string,
|
||||||
|
public readonly toolCallID?: string,
|
||||||
) {
|
) {
|
||||||
super(`The user rejected permission to use this functionality`)
|
super(`The user rejected permission to use this functionality`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { LSP } from "../lsp"
|
|||||||
import { MessageV2 } from "../session/message-v2"
|
import { MessageV2 } from "../session/message-v2"
|
||||||
import { Mode } from "../session/mode"
|
import { Mode } from "../session/mode"
|
||||||
import { callTui, TuiRoute } from "./tui"
|
import { callTui, TuiRoute } from "./tui"
|
||||||
|
import { Permission } from "../permission"
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
400: {
|
400: {
|
||||||
@@ -457,6 +458,39 @@ export namespace Server {
|
|||||||
return c.json(messages)
|
return c.json(messages)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.get(
|
||||||
|
"/session/:id/message/:messageID",
|
||||||
|
describeRoute({
|
||||||
|
description: "Get a message from a session",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Message",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(
|
||||||
|
z.object({
|
||||||
|
info: MessageV2.Info,
|
||||||
|
parts: MessageV2.Part.array(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
zValidator(
|
||||||
|
"param",
|
||||||
|
z.object({
|
||||||
|
id: z.string().openapi({ description: "Session ID" }),
|
||||||
|
messageID: z.string().openapi({ description: "Message ID" }),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const params = c.req.valid("param")
|
||||||
|
const message = await Session.getMessage(params.id, params.messageID)
|
||||||
|
return c.json(message)
|
||||||
|
},
|
||||||
|
)
|
||||||
.post(
|
.post(
|
||||||
"/session/:id/message",
|
"/session/:id/message",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
@@ -545,6 +579,37 @@ export namespace Server {
|
|||||||
return c.json(session)
|
return c.json(session)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
.post(
|
||||||
|
"/session/:id/permissions/:permissionID",
|
||||||
|
describeRoute({
|
||||||
|
description: "Respond to a permission request",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Permission processed successfully",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(z.boolean()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
zValidator(
|
||||||
|
"param",
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
permissionID: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
zValidator("json", z.object({ response: Permission.Response })),
|
||||||
|
async (c) => {
|
||||||
|
const params = c.req.valid("param")
|
||||||
|
const id = params.id
|
||||||
|
const permissionID = params.permissionID
|
||||||
|
Permission.respond({ sessionID: id, permissionID, response: c.req.valid("json").response })
|
||||||
|
return c.json(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
.get(
|
.get(
|
||||||
"/config/providers",
|
"/config/providers",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|||||||
@@ -256,7 +256,10 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getMessage(sessionID: string, messageID: string) {
|
export async function getMessage(sessionID: string, messageID: string) {
|
||||||
return Storage.readJSON<MessageV2.Info>("session/message/" + sessionID + "/" + messageID)
|
return {
|
||||||
|
info: await Storage.readJSON<MessageV2.Info>("session/message/" + sessionID + "/" + messageID),
|
||||||
|
parts: await getParts(sessionID, messageID),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getParts(sessionID: string, messageID: string) {
|
export async function getParts(sessionID: string, messageID: string) {
|
||||||
@@ -714,6 +717,7 @@ export namespace Session {
|
|||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
abort: abort.signal,
|
abort: abort.signal,
|
||||||
messageID: assistantMsg.id,
|
messageID: assistantMsg.id,
|
||||||
|
toolCallID: options.toolCallId,
|
||||||
metadata: async (val) => {
|
metadata: async (val) => {
|
||||||
const match = processor.partFromToolCall(options.toolCallId)
|
const match = processor.partFromToolCall(options.toolCallId)
|
||||||
if (match && match.state.status === "running") {
|
if (match && match.state.status === "running") {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { z } from "zod"
|
|||||||
import { Tool } from "./tool"
|
import { Tool } from "./tool"
|
||||||
import DESCRIPTION from "./bash.txt"
|
import DESCRIPTION from "./bash.txt"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
|
import { Permission } from "../permission"
|
||||||
|
import { Config } from "../config/config"
|
||||||
|
|
||||||
// import Parser from "tree-sitter"
|
// import Parser from "tree-sitter"
|
||||||
// import Bash from "tree-sitter-bash"
|
// import Bash from "tree-sitter-bash"
|
||||||
@@ -93,6 +95,8 @@ export const BashTool = Tool.define("bash", {
|
|||||||
await Permission.ask({
|
await Permission.ask({
|
||||||
id: "bash",
|
id: "bash",
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
|
messageID: ctx.messageID,
|
||||||
|
toolCallID: ctx.toolCallID,
|
||||||
title: params.command,
|
title: params.command,
|
||||||
metadata: {
|
metadata: {
|
||||||
command: params.command,
|
command: params.command,
|
||||||
@@ -101,6 +105,21 @@ export const BashTool = Tool.define("bash", {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const cfg = await Config.get()
|
||||||
|
if (cfg.permission?.bash === "ask")
|
||||||
|
await Permission.ask({
|
||||||
|
id: "bash",
|
||||||
|
sessionID: ctx.sessionID,
|
||||||
|
messageID: ctx.messageID,
|
||||||
|
toolCallID: ctx.toolCallID,
|
||||||
|
title: "Run this command: " + params.command,
|
||||||
|
metadata: {
|
||||||
|
command: params.command,
|
||||||
|
description: params.description,
|
||||||
|
timeout: params.timeout,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const process = Bun.spawn({
|
const process = Bun.spawn({
|
||||||
cmd: ["bash", "-c", params.command],
|
cmd: ["bash", "-c", params.command],
|
||||||
cwd: app.path.cwd,
|
cwd: app.path.cwd,
|
||||||
|
|||||||
@@ -35,61 +35,77 @@ export const EditTool = Tool.define("edit", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
const filePath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
||||||
if (!Filesystem.contains(app.path.cwd, filepath)) {
|
if (!Filesystem.contains(app.path.cwd, filePath)) {
|
||||||
throw new Error(`File ${filepath} is not in the current working directory`)
|
throw new Error(`File ${filePath} is not in the current working directory`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
if (cfg.permission?.edit === "ask")
|
let diff = ""
|
||||||
await Permission.ask({
|
|
||||||
id: "edit",
|
|
||||||
sessionID: ctx.sessionID,
|
|
||||||
title: "Edit this file: " + filepath,
|
|
||||||
metadata: {
|
|
||||||
filePath: filepath,
|
|
||||||
oldString: params.oldString,
|
|
||||||
newString: params.newString,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
let contentOld = ""
|
let contentOld = ""
|
||||||
let contentNew = ""
|
let contentNew = ""
|
||||||
await (async () => {
|
await (async () => {
|
||||||
if (params.oldString === "") {
|
if (params.oldString === "") {
|
||||||
contentNew = params.newString
|
contentNew = params.newString
|
||||||
await Bun.write(filepath, params.newString)
|
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||||
|
if (cfg.permission?.edit === "ask") {
|
||||||
|
await Permission.ask({
|
||||||
|
id: "edit",
|
||||||
|
sessionID: ctx.sessionID,
|
||||||
|
messageID: ctx.messageID,
|
||||||
|
toolCallID: ctx.toolCallID,
|
||||||
|
title: "Edit this file: " + filePath,
|
||||||
|
metadata: {
|
||||||
|
filePath,
|
||||||
|
diff,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await Bun.write(filePath, params.newString)
|
||||||
await Bus.publish(File.Event.Edited, {
|
await Bus.publish(File.Event.Edited, {
|
||||||
file: filepath,
|
file: filePath,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filePath)
|
||||||
const stats = await file.stat().catch(() => {})
|
const stats = await file.stat().catch(() => {})
|
||||||
if (!stats) throw new Error(`File ${filepath} not found`)
|
if (!stats) throw new Error(`File ${filePath} not found`)
|
||||||
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filepath}`)
|
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filePath}`)
|
||||||
await FileTime.assert(ctx.sessionID, filepath)
|
await FileTime.assert(ctx.sessionID, filePath)
|
||||||
contentOld = await file.text()
|
contentOld = await file.text()
|
||||||
|
|
||||||
contentNew = replace(contentOld, params.oldString, params.newString, params.replaceAll)
|
contentNew = replace(contentOld, params.oldString, params.newString, params.replaceAll)
|
||||||
|
|
||||||
|
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
|
||||||
|
if (cfg.permission?.edit === "ask") {
|
||||||
|
await Permission.ask({
|
||||||
|
id: "edit",
|
||||||
|
sessionID: ctx.sessionID,
|
||||||
|
messageID: ctx.messageID,
|
||||||
|
toolCallID: ctx.toolCallID,
|
||||||
|
title: "Edit this file: " + filePath,
|
||||||
|
metadata: {
|
||||||
|
filePath,
|
||||||
|
diff,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
await file.write(contentNew)
|
await file.write(contentNew)
|
||||||
await Bus.publish(File.Event.Edited, {
|
await Bus.publish(File.Event.Edited, {
|
||||||
file: filepath,
|
file: filePath,
|
||||||
})
|
})
|
||||||
contentNew = await file.text()
|
contentNew = await file.text()
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, contentNew))
|
FileTime.read(ctx.sessionID, filePath)
|
||||||
|
|
||||||
FileTime.read(ctx.sessionID, filepath)
|
|
||||||
|
|
||||||
let output = ""
|
let output = ""
|
||||||
await LSP.touchFile(filepath, true)
|
await LSP.touchFile(filePath, true)
|
||||||
const diagnostics = await LSP.diagnostics()
|
const diagnostics = await LSP.diagnostics()
|
||||||
for (const [file, issues] of Object.entries(diagnostics)) {
|
for (const [file, issues] of Object.entries(diagnostics)) {
|
||||||
if (issues.length === 0) continue
|
if (issues.length === 0) continue
|
||||||
if (file === filepath) {
|
if (file === filePath) {
|
||||||
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
|
output += `\nThis file has errors, please fix\n<file_diagnostics>\n${issues.map(LSP.Diagnostic.pretty).join("\n")}\n</file_diagnostics>\n`
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -104,7 +120,7 @@ export const EditTool = Tool.define("edit", {
|
|||||||
diagnostics,
|
diagnostics,
|
||||||
diff,
|
diff,
|
||||||
},
|
},
|
||||||
title: `${path.relative(app.path.root, filepath)}`,
|
title: `${path.relative(app.path.root, filePath)}`,
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export const TaskTool = Tool.define("task", async () => {
|
|||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
const session = await Session.create(ctx.sessionID)
|
const session = await Session.create(ctx.sessionID)
|
||||||
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
||||||
if (msg.role !== "assistant") throw new Error("Not an assistant message")
|
if (msg.info.role !== "assistant") throw new Error("Not an assistant message")
|
||||||
const agent = await Agent.get(params.subagent_type)
|
const agent = await Agent.get(params.subagent_type)
|
||||||
const messageID = Identifier.ascending("message")
|
const messageID = Identifier.ascending("message")
|
||||||
const parts: Record<string, MessageV2.ToolPart> = {}
|
const parts: Record<string, MessageV2.ToolPart> = {}
|
||||||
@@ -38,8 +38,8 @@ export const TaskTool = Tool.define("task", async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const model = agent.model ?? {
|
const model = agent.model ?? {
|
||||||
modelID: msg.modelID,
|
modelID: msg.info.modelID,
|
||||||
providerID: msg.providerID,
|
providerID: msg.info.providerID,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.abort.addEventListener("abort", () => {
|
ctx.abort.addEventListener("abort", () => {
|
||||||
@@ -50,7 +50,7 @@ export const TaskTool = Tool.define("task", async () => {
|
|||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
modelID: model.modelID,
|
modelID: model.modelID,
|
||||||
providerID: model.providerID,
|
providerID: model.providerID,
|
||||||
mode: msg.mode,
|
mode: msg.info.mode,
|
||||||
system: agent.prompt,
|
system: agent.prompt,
|
||||||
tools: {
|
tools: {
|
||||||
...agent.tools,
|
...agent.tools,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export namespace Tool {
|
|||||||
export type Context<M extends Metadata = Metadata> = {
|
export type Context<M extends Metadata = Metadata> = {
|
||||||
sessionID: string
|
sessionID: string
|
||||||
messageID: string
|
messageID: string
|
||||||
|
toolCallID: string
|
||||||
abort: AbortSignal
|
abort: AbortSignal
|
||||||
metadata(input: { title?: string; metadata?: M }): void
|
metadata(input: { title?: string; metadata?: M }): void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ export const WriteTool = Tool.define("write", {
|
|||||||
await Permission.ask({
|
await Permission.ask({
|
||||||
id: "write",
|
id: "write",
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
|
messageID: ctx.messageID,
|
||||||
|
toolCallID: ctx.toolCallID,
|
||||||
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
|
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
|
||||||
metadata: {
|
metadata: {
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
|
|||||||
44
packages/sdk/src/resources/session/index.ts
Normal file
44
packages/sdk/src/resources/session/index.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
export {
|
||||||
|
Permissions,
|
||||||
|
type Permission,
|
||||||
|
type PermissionRespondResponse,
|
||||||
|
type PermissionRespondParams,
|
||||||
|
} from './permissions';
|
||||||
|
export {
|
||||||
|
SessionResource,
|
||||||
|
type AssistantMessage,
|
||||||
|
type FilePart,
|
||||||
|
type FilePartInput,
|
||||||
|
type FilePartSource,
|
||||||
|
type FilePartSourceText,
|
||||||
|
type FileSource,
|
||||||
|
type Message,
|
||||||
|
type Part,
|
||||||
|
type Session,
|
||||||
|
type SnapshotPart,
|
||||||
|
type StepFinishPart,
|
||||||
|
type StepStartPart,
|
||||||
|
type SymbolSource,
|
||||||
|
type TextPart,
|
||||||
|
type TextPartInput,
|
||||||
|
type ToolPart,
|
||||||
|
type ToolStateCompleted,
|
||||||
|
type ToolStateError,
|
||||||
|
type ToolStatePending,
|
||||||
|
type ToolStateRunning,
|
||||||
|
type UserMessage,
|
||||||
|
type SessionListResponse,
|
||||||
|
type SessionDeleteResponse,
|
||||||
|
type SessionAbortResponse,
|
||||||
|
type SessionInitResponse,
|
||||||
|
type SessionMessageResponse,
|
||||||
|
type SessionMessagesResponse,
|
||||||
|
type SessionSummarizeResponse,
|
||||||
|
type SessionChatParams,
|
||||||
|
type SessionInitParams,
|
||||||
|
type SessionMessageParams,
|
||||||
|
type SessionRevertParams,
|
||||||
|
type SessionSummarizeParams,
|
||||||
|
} from './session';
|
||||||
64
packages/sdk/src/resources/session/permissions.ts
Normal file
64
packages/sdk/src/resources/session/permissions.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
import { APIResource } from '../../core/resource';
|
||||||
|
import { APIPromise } from '../../core/api-promise';
|
||||||
|
import { RequestOptions } from '../../internal/request-options';
|
||||||
|
import { path } from '../../internal/utils/path';
|
||||||
|
|
||||||
|
export class Permissions extends APIResource {
|
||||||
|
/**
|
||||||
|
* Respond to a permission request
|
||||||
|
*/
|
||||||
|
respond(
|
||||||
|
permissionID: string,
|
||||||
|
params: PermissionRespondParams,
|
||||||
|
options?: RequestOptions,
|
||||||
|
): APIPromise<PermissionRespondResponse> {
|
||||||
|
const { id, ...body } = params;
|
||||||
|
return this._client.post(path`/session/${id}/permissions/${permissionID}`, { body, ...options });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Permission {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
metadata: { [key: string]: unknown };
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
time: Permission.Time;
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
toolCallID?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Permission {
|
||||||
|
export interface Time {
|
||||||
|
created: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PermissionRespondResponse = boolean;
|
||||||
|
|
||||||
|
export interface PermissionRespondParams {
|
||||||
|
/**
|
||||||
|
* Path param:
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Body param:
|
||||||
|
*/
|
||||||
|
response: 'once' | 'always' | 'reject';
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare namespace Permissions {
|
||||||
|
export {
|
||||||
|
type Permission as Permission,
|
||||||
|
type PermissionRespondResponse as PermissionRespondResponse,
|
||||||
|
type PermissionRespondParams as PermissionRespondParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
645
packages/sdk/src/resources/session/session.ts
Normal file
645
packages/sdk/src/resources/session/session.ts
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
import { APIResource } from '../../core/resource';
|
||||||
|
import * as SessionAPI from './session';
|
||||||
|
import * as Shared from '../shared';
|
||||||
|
import * as PermissionsAPI from './permissions';
|
||||||
|
import { Permission, PermissionRespondParams, PermissionRespondResponse, Permissions } from './permissions';
|
||||||
|
import { APIPromise } from '../../core/api-promise';
|
||||||
|
import { RequestOptions } from '../../internal/request-options';
|
||||||
|
import { path } from '../../internal/utils/path';
|
||||||
|
|
||||||
|
export class SessionResource extends APIResource {
|
||||||
|
permissions: PermissionsAPI.Permissions = new PermissionsAPI.Permissions(this._client);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new session
|
||||||
|
*/
|
||||||
|
create(options?: RequestOptions): APIPromise<Session> {
|
||||||
|
return this._client.post('/session', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all sessions
|
||||||
|
*/
|
||||||
|
list(options?: RequestOptions): APIPromise<SessionListResponse> {
|
||||||
|
return this._client.get('/session', options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a session and all its data
|
||||||
|
*/
|
||||||
|
delete(id: string, options?: RequestOptions): APIPromise<SessionDeleteResponse> {
|
||||||
|
return this._client.delete(path`/session/${id}`, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abort a session
|
||||||
|
*/
|
||||||
|
abort(id: string, options?: RequestOptions): APIPromise<SessionAbortResponse> {
|
||||||
|
return this._client.post(path`/session/${id}/abort`, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and send a new message to a session
|
||||||
|
*/
|
||||||
|
chat(id: string, body: SessionChatParams, options?: RequestOptions): APIPromise<AssistantMessage> {
|
||||||
|
return this._client.post(path`/session/${id}/message`, { body, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze the app and create an AGENTS.md file
|
||||||
|
*/
|
||||||
|
init(id: string, body: SessionInitParams, options?: RequestOptions): APIPromise<SessionInitResponse> {
|
||||||
|
return this._client.post(path`/session/${id}/init`, { body, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a message from a session
|
||||||
|
*/
|
||||||
|
message(
|
||||||
|
messageID: string,
|
||||||
|
params: SessionMessageParams,
|
||||||
|
options?: RequestOptions,
|
||||||
|
): APIPromise<SessionMessageResponse> {
|
||||||
|
const { id } = params;
|
||||||
|
return this._client.get(path`/session/${id}/message/${messageID}`, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List messages for a session
|
||||||
|
*/
|
||||||
|
messages(id: string, options?: RequestOptions): APIPromise<SessionMessagesResponse> {
|
||||||
|
return this._client.get(path`/session/${id}/message`, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revert a message
|
||||||
|
*/
|
||||||
|
revert(id: string, body: SessionRevertParams, options?: RequestOptions): APIPromise<Session> {
|
||||||
|
return this._client.post(path`/session/${id}/revert`, { body, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Share a session
|
||||||
|
*/
|
||||||
|
share(id: string, options?: RequestOptions): APIPromise<Session> {
|
||||||
|
return this._client.post(path`/session/${id}/share`, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Summarize the session
|
||||||
|
*/
|
||||||
|
summarize(
|
||||||
|
id: string,
|
||||||
|
body: SessionSummarizeParams,
|
||||||
|
options?: RequestOptions,
|
||||||
|
): APIPromise<SessionSummarizeResponse> {
|
||||||
|
return this._client.post(path`/session/${id}/summarize`, { body, ...options });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore all reverted messages
|
||||||
|
*/
|
||||||
|
unrevert(id: string, options?: RequestOptions): APIPromise<Session> {
|
||||||
|
return this._client.post(path`/session/${id}/unrevert`, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unshare the session
|
||||||
|
*/
|
||||||
|
unshare(id: string, options?: RequestOptions): APIPromise<Session> {
|
||||||
|
return this._client.delete(path`/session/${id}/share`, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AssistantMessage {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
cost: number;
|
||||||
|
|
||||||
|
mode: string;
|
||||||
|
|
||||||
|
modelID: string;
|
||||||
|
|
||||||
|
path: AssistantMessage.Path;
|
||||||
|
|
||||||
|
providerID: string;
|
||||||
|
|
||||||
|
role: 'assistant';
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
system: Array<string>;
|
||||||
|
|
||||||
|
time: AssistantMessage.Time;
|
||||||
|
|
||||||
|
tokens: AssistantMessage.Tokens;
|
||||||
|
|
||||||
|
error?:
|
||||||
|
| Shared.ProviderAuthError
|
||||||
|
| Shared.UnknownError
|
||||||
|
| AssistantMessage.MessageOutputLengthError
|
||||||
|
| Shared.MessageAbortedError;
|
||||||
|
|
||||||
|
summary?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace AssistantMessage {
|
||||||
|
export interface Path {
|
||||||
|
cwd: string;
|
||||||
|
|
||||||
|
root: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Time {
|
||||||
|
created: number;
|
||||||
|
|
||||||
|
completed?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tokens {
|
||||||
|
cache: Tokens.Cache;
|
||||||
|
|
||||||
|
input: number;
|
||||||
|
|
||||||
|
output: number;
|
||||||
|
|
||||||
|
reasoning: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Tokens {
|
||||||
|
export interface Cache {
|
||||||
|
read: number;
|
||||||
|
|
||||||
|
write: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageOutputLengthError {
|
||||||
|
data: unknown;
|
||||||
|
|
||||||
|
name: 'MessageOutputLengthError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilePart {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
mime: string;
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
type: 'file';
|
||||||
|
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
filename?: string;
|
||||||
|
|
||||||
|
source?: FilePartSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FilePartInput {
|
||||||
|
mime: string;
|
||||||
|
|
||||||
|
type: 'file';
|
||||||
|
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
id?: string;
|
||||||
|
|
||||||
|
filename?: string;
|
||||||
|
|
||||||
|
source?: FilePartSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FilePartSource = FileSource | SymbolSource;
|
||||||
|
|
||||||
|
export interface FilePartSourceText {
|
||||||
|
end: number;
|
||||||
|
|
||||||
|
start: number;
|
||||||
|
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileSource {
|
||||||
|
path: string;
|
||||||
|
|
||||||
|
text: FilePartSourceText;
|
||||||
|
|
||||||
|
type: 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Message = UserMessage | AssistantMessage;
|
||||||
|
|
||||||
|
export type Part =
|
||||||
|
| TextPart
|
||||||
|
| FilePart
|
||||||
|
| ToolPart
|
||||||
|
| StepStartPart
|
||||||
|
| StepFinishPart
|
||||||
|
| SnapshotPart
|
||||||
|
| Part.PatchPart;
|
||||||
|
|
||||||
|
export namespace Part {
|
||||||
|
export interface PatchPart {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
files: Array<string>;
|
||||||
|
|
||||||
|
hash: string;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
type: 'patch';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Session {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
time: Session.Time;
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
parentID?: string;
|
||||||
|
|
||||||
|
revert?: Session.Revert;
|
||||||
|
|
||||||
|
share?: Session.Share;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Session {
|
||||||
|
export interface Time {
|
||||||
|
created: number;
|
||||||
|
|
||||||
|
updated: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Revert {
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
diff?: string;
|
||||||
|
|
||||||
|
partID?: string;
|
||||||
|
|
||||||
|
snapshot?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Share {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SnapshotPart {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
snapshot: string;
|
||||||
|
|
||||||
|
type: 'snapshot';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StepFinishPart {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
cost: number;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
tokens: StepFinishPart.Tokens;
|
||||||
|
|
||||||
|
type: 'step-finish';
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace StepFinishPart {
|
||||||
|
export interface Tokens {
|
||||||
|
cache: Tokens.Cache;
|
||||||
|
|
||||||
|
input: number;
|
||||||
|
|
||||||
|
output: number;
|
||||||
|
|
||||||
|
reasoning: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Tokens {
|
||||||
|
export interface Cache {
|
||||||
|
read: number;
|
||||||
|
|
||||||
|
write: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StepStartPart {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
type: 'step-start';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SymbolSource {
|
||||||
|
kind: number;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
path: string;
|
||||||
|
|
||||||
|
range: SymbolSource.Range;
|
||||||
|
|
||||||
|
text: FilePartSourceText;
|
||||||
|
|
||||||
|
type: 'symbol';
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace SymbolSource {
|
||||||
|
export interface Range {
|
||||||
|
end: Range.End;
|
||||||
|
|
||||||
|
start: Range.Start;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Range {
|
||||||
|
export interface End {
|
||||||
|
character: number;
|
||||||
|
|
||||||
|
line: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Start {
|
||||||
|
character: number;
|
||||||
|
|
||||||
|
line: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextPart {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
type: 'text';
|
||||||
|
|
||||||
|
synthetic?: boolean;
|
||||||
|
|
||||||
|
time?: TextPart.Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace TextPart {
|
||||||
|
export interface Time {
|
||||||
|
start: number;
|
||||||
|
|
||||||
|
end?: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextPartInput {
|
||||||
|
text: string;
|
||||||
|
|
||||||
|
type: 'text';
|
||||||
|
|
||||||
|
id?: string;
|
||||||
|
|
||||||
|
synthetic?: boolean;
|
||||||
|
|
||||||
|
time?: TextPartInput.Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace TextPartInput {
|
||||||
|
export interface Time {
|
||||||
|
start: number;
|
||||||
|
|
||||||
|
end?: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolPart {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
callID: string;
|
||||||
|
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
state: ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError;
|
||||||
|
|
||||||
|
tool: string;
|
||||||
|
|
||||||
|
type: 'tool';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolStateCompleted {
|
||||||
|
input: { [key: string]: unknown };
|
||||||
|
|
||||||
|
metadata: { [key: string]: unknown };
|
||||||
|
|
||||||
|
output: string;
|
||||||
|
|
||||||
|
status: 'completed';
|
||||||
|
|
||||||
|
time: ToolStateCompleted.Time;
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ToolStateCompleted {
|
||||||
|
export interface Time {
|
||||||
|
end: number;
|
||||||
|
|
||||||
|
start: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolStateError {
|
||||||
|
error: string;
|
||||||
|
|
||||||
|
input: { [key: string]: unknown };
|
||||||
|
|
||||||
|
status: 'error';
|
||||||
|
|
||||||
|
time: ToolStateError.Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ToolStateError {
|
||||||
|
export interface Time {
|
||||||
|
end: number;
|
||||||
|
|
||||||
|
start: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolStatePending {
|
||||||
|
status: 'pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolStateRunning {
|
||||||
|
status: 'running';
|
||||||
|
|
||||||
|
time: ToolStateRunning.Time;
|
||||||
|
|
||||||
|
input?: unknown;
|
||||||
|
|
||||||
|
metadata?: { [key: string]: unknown };
|
||||||
|
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ToolStateRunning {
|
||||||
|
export interface Time {
|
||||||
|
start: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserMessage {
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
role: 'user';
|
||||||
|
|
||||||
|
sessionID: string;
|
||||||
|
|
||||||
|
time: UserMessage.Time;
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace UserMessage {
|
||||||
|
export interface Time {
|
||||||
|
created: number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionListResponse = Array<Session>;
|
||||||
|
|
||||||
|
export type SessionDeleteResponse = boolean;
|
||||||
|
|
||||||
|
export type SessionAbortResponse = boolean;
|
||||||
|
|
||||||
|
export type SessionInitResponse = boolean;
|
||||||
|
|
||||||
|
export interface SessionMessageResponse {
|
||||||
|
info: Message;
|
||||||
|
|
||||||
|
parts: Array<Part>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionMessagesResponse = Array<SessionMessagesResponse.SessionMessagesResponseItem>;
|
||||||
|
|
||||||
|
export namespace SessionMessagesResponse {
|
||||||
|
export interface SessionMessagesResponseItem {
|
||||||
|
info: SessionAPI.Message;
|
||||||
|
|
||||||
|
parts: Array<SessionAPI.Part>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionSummarizeResponse = boolean;
|
||||||
|
|
||||||
|
export interface SessionChatParams {
|
||||||
|
modelID: string;
|
||||||
|
|
||||||
|
parts: Array<TextPartInput | FilePartInput>;
|
||||||
|
|
||||||
|
providerID: string;
|
||||||
|
|
||||||
|
messageID?: string;
|
||||||
|
|
||||||
|
mode?: string;
|
||||||
|
|
||||||
|
system?: string;
|
||||||
|
|
||||||
|
tools?: { [key: string]: boolean };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionInitParams {
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
modelID: string;
|
||||||
|
|
||||||
|
providerID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionMessageParams {
|
||||||
|
/**
|
||||||
|
* Session ID
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionRevertParams {
|
||||||
|
messageID: string;
|
||||||
|
|
||||||
|
partID?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SessionSummarizeParams {
|
||||||
|
modelID: string;
|
||||||
|
|
||||||
|
providerID: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
SessionResource.Permissions = Permissions;
|
||||||
|
|
||||||
|
export declare namespace SessionResource {
|
||||||
|
export {
|
||||||
|
type AssistantMessage as AssistantMessage,
|
||||||
|
type FilePart as FilePart,
|
||||||
|
type FilePartInput as FilePartInput,
|
||||||
|
type FilePartSource as FilePartSource,
|
||||||
|
type FilePartSourceText as FilePartSourceText,
|
||||||
|
type FileSource as FileSource,
|
||||||
|
type Message as Message,
|
||||||
|
type Part as Part,
|
||||||
|
type Session as Session,
|
||||||
|
type SnapshotPart as SnapshotPart,
|
||||||
|
type StepFinishPart as StepFinishPart,
|
||||||
|
type StepStartPart as StepStartPart,
|
||||||
|
type SymbolSource as SymbolSource,
|
||||||
|
type TextPart as TextPart,
|
||||||
|
type TextPartInput as TextPartInput,
|
||||||
|
type ToolPart as ToolPart,
|
||||||
|
type ToolStateCompleted as ToolStateCompleted,
|
||||||
|
type ToolStateError as ToolStateError,
|
||||||
|
type ToolStatePending as ToolStatePending,
|
||||||
|
type ToolStateRunning as ToolStateRunning,
|
||||||
|
type UserMessage as UserMessage,
|
||||||
|
type SessionListResponse as SessionListResponse,
|
||||||
|
type SessionDeleteResponse as SessionDeleteResponse,
|
||||||
|
type SessionAbortResponse as SessionAbortResponse,
|
||||||
|
type SessionInitResponse as SessionInitResponse,
|
||||||
|
type SessionMessageResponse as SessionMessageResponse,
|
||||||
|
type SessionMessagesResponse as SessionMessagesResponse,
|
||||||
|
type SessionSummarizeResponse as SessionSummarizeResponse,
|
||||||
|
type SessionChatParams as SessionChatParams,
|
||||||
|
type SessionInitParams as SessionInitParams,
|
||||||
|
type SessionMessageParams as SessionMessageParams,
|
||||||
|
type SessionRevertParams as SessionRevertParams,
|
||||||
|
type SessionSummarizeParams as SessionSummarizeParams,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
Permissions as Permissions,
|
||||||
|
type Permission as Permission,
|
||||||
|
type PermissionRespondResponse as PermissionRespondResponse,
|
||||||
|
type PermissionRespondParams as PermissionRespondParams,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -118,11 +118,19 @@ resources:
|
|||||||
share: post /session/{id}/share
|
share: post /session/{id}/share
|
||||||
unshare: delete /session/{id}/share
|
unshare: delete /session/{id}/share
|
||||||
summarize: post /session/{id}/summarize
|
summarize: post /session/{id}/summarize
|
||||||
|
message: get /session/{id}/message/{messageID}
|
||||||
messages: get /session/{id}/message
|
messages: get /session/{id}/message
|
||||||
chat: post /session/{id}/message
|
chat: post /session/{id}/message
|
||||||
revert: post /session/{id}/revert
|
revert: post /session/{id}/revert
|
||||||
unrevert: post /session/{id}/unrevert
|
unrevert: post /session/{id}/unrevert
|
||||||
|
|
||||||
|
subresources:
|
||||||
|
permissions:
|
||||||
|
models:
|
||||||
|
permission: Permission
|
||||||
|
methods:
|
||||||
|
respond: post /session/{id}/permissions/{permissionID}
|
||||||
|
|
||||||
tui:
|
tui:
|
||||||
methods:
|
methods:
|
||||||
appendPrompt: post /tui/append-prompt
|
appendPrompt: post /tui/append-prompt
|
||||||
|
|||||||
27
packages/sdk/tests/api-resources/session/permissions.test.ts
Normal file
27
packages/sdk/tests/api-resources/session/permissions.test.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
import Opencode from '@opencode-ai/sdk';
|
||||||
|
|
||||||
|
const client = new Opencode({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010' });
|
||||||
|
|
||||||
|
describe('resource permissions', () => {
|
||||||
|
// skipped: tests are disabled for the time being
|
||||||
|
test.skip('respond: only required params', async () => {
|
||||||
|
const responsePromise = client.session.permissions.respond('permissionID', {
|
||||||
|
id: 'id',
|
||||||
|
response: 'once',
|
||||||
|
});
|
||||||
|
const rawResponse = await responsePromise.asResponse();
|
||||||
|
expect(rawResponse).toBeInstanceOf(Response);
|
||||||
|
const response = await responsePromise;
|
||||||
|
expect(response).not.toBeInstanceOf(Response);
|
||||||
|
const dataAndResponse = await responsePromise.withResponse();
|
||||||
|
expect(dataAndResponse.data).toBe(response);
|
||||||
|
expect(dataAndResponse.response).toBe(rawResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
// skipped: tests are disabled for the time being
|
||||||
|
test.skip('respond: required and optional params', async () => {
|
||||||
|
const response = await client.session.permissions.respond('permissionID', { id: 'id', response: 'once' });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -40,6 +40,8 @@ type App struct {
|
|||||||
Model *opencode.Model
|
Model *opencode.Model
|
||||||
Session *opencode.Session
|
Session *opencode.Session
|
||||||
Messages []Message
|
Messages []Message
|
||||||
|
Permissions []opencode.Permission
|
||||||
|
CurrentPermission opencode.Permission
|
||||||
Commands commands.CommandRegistry
|
Commands commands.CommandRegistry
|
||||||
InitialModel *string
|
InitialModel *string
|
||||||
InitialPrompt *string
|
InitialPrompt *string
|
||||||
|
|||||||
@@ -344,9 +344,13 @@ func (m *editorComponent) Content() string {
|
|||||||
hint = base(keyText+" again") + muted(" to exit")
|
hint = base(keyText+" again") + muted(" to exit")
|
||||||
} else if m.app.IsBusy() {
|
} else if m.app.IsBusy() {
|
||||||
keyText := m.getInterruptKeyText()
|
keyText := m.getInterruptKeyText()
|
||||||
if m.interruptKeyInDebounce {
|
status := "working"
|
||||||
|
if m.app.CurrentPermission.ID != "" {
|
||||||
|
status = "waiting for permission"
|
||||||
|
}
|
||||||
|
if m.interruptKeyInDebounce && m.app.CurrentPermission.ID == "" {
|
||||||
hint = muted(
|
hint = muted(
|
||||||
"working",
|
status,
|
||||||
) + m.spinner.View() + muted(
|
) + m.spinner.View() + muted(
|
||||||
" ",
|
" ",
|
||||||
) + base(
|
) + base(
|
||||||
@@ -355,7 +359,10 @@ func (m *editorComponent) Content() string {
|
|||||||
" interrupt",
|
" interrupt",
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
hint = muted("working") + m.spinner.View() + muted(" ") + base(keyText) + muted(" interrupt")
|
hint = muted(status) + m.spinner.View()
|
||||||
|
if m.app.CurrentPermission.ID == "" {
|
||||||
|
hint += muted(" ") + base(keyText) + muted(" interrupt")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package chat
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,7 +26,8 @@ type blockRenderer struct {
|
|||||||
textColor compat.AdaptiveColor
|
textColor compat.AdaptiveColor
|
||||||
border bool
|
border bool
|
||||||
borderColor *compat.AdaptiveColor
|
borderColor *compat.AdaptiveColor
|
||||||
borderColorRight bool
|
borderLeft bool
|
||||||
|
borderRight bool
|
||||||
paddingTop int
|
paddingTop int
|
||||||
paddingBottom int
|
paddingBottom int
|
||||||
paddingLeft int
|
paddingLeft int
|
||||||
@@ -54,10 +56,26 @@ func WithBorderColor(color compat.AdaptiveColor) renderingOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithBorderColorRight(color compat.AdaptiveColor) renderingOption {
|
func WithBorderLeft() renderingOption {
|
||||||
return func(c *blockRenderer) {
|
return func(c *blockRenderer) {
|
||||||
c.borderColorRight = true
|
c.borderLeft = true
|
||||||
c.borderColor = &color
|
c.borderRight = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBorderRight() renderingOption {
|
||||||
|
return func(c *blockRenderer) {
|
||||||
|
c.borderLeft = false
|
||||||
|
c.borderRight = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithBorderBoth(value bool) renderingOption {
|
||||||
|
return func(c *blockRenderer) {
|
||||||
|
if value {
|
||||||
|
c.borderLeft = true
|
||||||
|
c.borderRight = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +134,8 @@ func renderContentBlock(
|
|||||||
renderer := &blockRenderer{
|
renderer := &blockRenderer{
|
||||||
textColor: t.TextMuted(),
|
textColor: t.TextMuted(),
|
||||||
border: true,
|
border: true,
|
||||||
|
borderLeft: true,
|
||||||
|
borderRight: false,
|
||||||
paddingTop: 1,
|
paddingTop: 1,
|
||||||
paddingBottom: 1,
|
paddingBottom: 1,
|
||||||
paddingLeft: 2,
|
paddingLeft: 2,
|
||||||
@@ -144,19 +164,17 @@ func renderContentBlock(
|
|||||||
BorderStyle(lipgloss.ThickBorder()).
|
BorderStyle(lipgloss.ThickBorder()).
|
||||||
BorderLeft(true).
|
BorderLeft(true).
|
||||||
BorderRight(true).
|
BorderRight(true).
|
||||||
BorderLeftForeground(borderColor).
|
BorderLeftForeground(t.BackgroundPanel()).
|
||||||
BorderLeftBackground(t.Background()).
|
BorderLeftBackground(t.Background()).
|
||||||
BorderRightForeground(t.BackgroundPanel()).
|
BorderRightForeground(t.BackgroundPanel()).
|
||||||
BorderRightBackground(t.Background())
|
BorderRightBackground(t.Background())
|
||||||
|
|
||||||
if renderer.borderColorRight {
|
if renderer.borderLeft {
|
||||||
style = style.
|
style = style.BorderLeftForeground(borderColor)
|
||||||
BorderLeftBackground(t.Background()).
|
}
|
||||||
BorderLeftForeground(t.BackgroundPanel()).
|
if renderer.borderRight {
|
||||||
BorderRightForeground(borderColor).
|
style = style.BorderRightForeground(borderColor)
|
||||||
BorderRightBackground(t.Background())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content = style.Render(content)
|
content = style.Render(content)
|
||||||
@@ -223,7 +241,7 @@ func renderText(
|
|||||||
if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 {
|
if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 {
|
||||||
content = content + "\n\n"
|
content = content + "\n\n"
|
||||||
for _, toolCall := range toolCalls {
|
for _, toolCall := range toolCalls {
|
||||||
title := renderToolTitle(toolCall, width)
|
title := renderToolTitle(toolCall, width-2)
|
||||||
style := styles.NewStyle()
|
style := styles.NewStyle()
|
||||||
if toolCall.State.Status == opencode.ToolPartStateStatusError {
|
if toolCall.State.Status == opencode.ToolPartStateStatusError {
|
||||||
style = style.Foreground(t.Error())
|
style = style.Foreground(t.Error())
|
||||||
@@ -247,7 +265,8 @@ func renderText(
|
|||||||
content,
|
content,
|
||||||
width,
|
width,
|
||||||
WithTextColor(t.Text()),
|
WithTextColor(t.Text()),
|
||||||
WithBorderColorRight(t.Secondary()),
|
WithBorderColor(t.Secondary()),
|
||||||
|
WithBorderRight(),
|
||||||
)
|
)
|
||||||
case opencode.AssistantMessage:
|
case opencode.AssistantMessage:
|
||||||
return renderContentBlock(
|
return renderContentBlock(
|
||||||
@@ -263,6 +282,7 @@ func renderText(
|
|||||||
func renderToolDetails(
|
func renderToolDetails(
|
||||||
app *app.App,
|
app *app.App,
|
||||||
toolCall opencode.ToolPart,
|
toolCall opencode.ToolPart,
|
||||||
|
permission opencode.Permission,
|
||||||
width int,
|
width int,
|
||||||
) string {
|
) string {
|
||||||
measure := util.Measure("chat.renderToolDetails")
|
measure := util.Measure("chat.renderToolDetails")
|
||||||
@@ -301,6 +321,39 @@ func renderToolDetails(
|
|||||||
borderColor := t.BackgroundPanel()
|
borderColor := t.BackgroundPanel()
|
||||||
defaultStyle := styles.NewStyle().Background(backgroundColor).Width(width - 6).Render
|
defaultStyle := styles.NewStyle().Background(backgroundColor).Width(width - 6).Render
|
||||||
|
|
||||||
|
permissionContent := ""
|
||||||
|
if permission.ID != "" {
|
||||||
|
borderColor = t.Warning()
|
||||||
|
|
||||||
|
base := styles.NewStyle().Background(backgroundColor)
|
||||||
|
text := base.Foreground(t.Text()).Bold(true).Render
|
||||||
|
muted := base.Foreground(t.TextMuted()).Render
|
||||||
|
permissionContent = "Permission required to run this tool:\n\n"
|
||||||
|
permissionContent += text(
|
||||||
|
"enter ",
|
||||||
|
) + muted(
|
||||||
|
"accept ",
|
||||||
|
) + text(
|
||||||
|
"a",
|
||||||
|
) + muted(
|
||||||
|
" accept always ",
|
||||||
|
) + text(
|
||||||
|
"esc",
|
||||||
|
) + muted(
|
||||||
|
" reject",
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if permission.Metadata != nil {
|
||||||
|
metadata := toolCall.State.Metadata.(map[string]any)
|
||||||
|
if metadata == nil {
|
||||||
|
metadata = map[string]any{}
|
||||||
|
}
|
||||||
|
maps.Copy(metadata, permission.Metadata)
|
||||||
|
toolCall.State.Metadata = metadata
|
||||||
|
}
|
||||||
|
|
||||||
if toolCall.State.Metadata != nil {
|
if toolCall.State.Metadata != nil {
|
||||||
metadata := toolCall.State.Metadata.(map[string]any)
|
metadata := toolCall.State.Metadata.(map[string]any)
|
||||||
switch toolCall.Tool {
|
switch toolCall.Tool {
|
||||||
@@ -351,12 +404,20 @@ func renderToolDetails(
|
|||||||
title := renderToolTitle(toolCall, width)
|
title := renderToolTitle(toolCall, width)
|
||||||
title = style.Render(title)
|
title = style.Render(title)
|
||||||
content := title + "\n" + body
|
content := title + "\n" + body
|
||||||
|
if permissionContent != "" {
|
||||||
|
permissionContent = styles.NewStyle().
|
||||||
|
Background(backgroundColor).
|
||||||
|
Padding(1, 2).
|
||||||
|
Render(permissionContent)
|
||||||
|
content += "\n" + permissionContent
|
||||||
|
}
|
||||||
content = renderContentBlock(
|
content = renderContentBlock(
|
||||||
app,
|
app,
|
||||||
content,
|
content,
|
||||||
width,
|
width,
|
||||||
WithPadding(0),
|
WithPadding(0),
|
||||||
WithBorderColor(borderColor),
|
WithBorderColor(borderColor),
|
||||||
|
WithBorderBoth(permission.ID != ""),
|
||||||
)
|
)
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
@@ -417,7 +478,7 @@ func renderToolDetails(
|
|||||||
data, _ := json.Marshal(item)
|
data, _ := json.Marshal(item)
|
||||||
var toolCall opencode.ToolPart
|
var toolCall opencode.ToolPart
|
||||||
_ = json.Unmarshal(data, &toolCall)
|
_ = json.Unmarshal(data, &toolCall)
|
||||||
step := renderToolTitle(toolCall, width)
|
step := renderToolTitle(toolCall, width-2)
|
||||||
step = "∟ " + step
|
step = "∟ " + step
|
||||||
steps = append(steps, step)
|
steps = append(steps, step)
|
||||||
}
|
}
|
||||||
@@ -460,7 +521,18 @@ func renderToolDetails(
|
|||||||
|
|
||||||
title := renderToolTitle(toolCall, width)
|
title := renderToolTitle(toolCall, width)
|
||||||
content := title + "\n\n" + body
|
content := title + "\n\n" + body
|
||||||
return renderContentBlock(app, content, width, WithBorderColor(borderColor))
|
|
||||||
|
if permissionContent != "" {
|
||||||
|
content += "\n\n\n" + permissionContent
|
||||||
|
}
|
||||||
|
|
||||||
|
return renderContentBlock(
|
||||||
|
app,
|
||||||
|
content,
|
||||||
|
width,
|
||||||
|
WithBorderColor(borderColor),
|
||||||
|
WithBorderBoth(permission.ID != ""),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderToolName(name string) string {
|
func renderToolName(name string) string {
|
||||||
@@ -575,6 +647,10 @@ func renderToolTitle(
|
|||||||
}
|
}
|
||||||
|
|
||||||
title = truncate.StringWithTail(title, uint(width-6), "...")
|
title = truncate.StringWithTail(title, uint(width-6), "...")
|
||||||
|
if toolCall.State.Error != "" {
|
||||||
|
t := theme.CurrentTheme()
|
||||||
|
title = styles.NewStyle().Foreground(t.Error()).Render(title)
|
||||||
|
}
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,8 +100,6 @@ func (m *messagesComponent) Init() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
measure := util.Measure("messages.Update")
|
|
||||||
defer measure("from", fmt.Sprintf("%T", msg))
|
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.MouseClickMsg:
|
case tea.MouseClickMsg:
|
||||||
@@ -199,6 +197,9 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.cache.Clear()
|
m.cache.Clear()
|
||||||
cmds = append(cmds, m.renderView())
|
cmds = append(cmds, m.renderView())
|
||||||
}
|
}
|
||||||
|
case opencode.EventListResponseEventPermissionUpdated:
|
||||||
|
m.tail = true
|
||||||
|
return m, m.renderView()
|
||||||
case renderCompleteMsg:
|
case renderCompleteMsg:
|
||||||
m.partCount = msg.partCount
|
m.partCount = msg.partCount
|
||||||
m.lineCount = msg.lineCount
|
m.lineCount = msg.lineCount
|
||||||
@@ -214,6 +215,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m.tail = m.viewport.AtBottom()
|
m.tail = m.viewport.AtBottom()
|
||||||
|
|
||||||
viewport, cmd := m.viewport.Update(msg)
|
viewport, cmd := m.viewport.Update(msg)
|
||||||
m.viewport = viewport
|
m.viewport = viewport
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
@@ -465,7 +467,13 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
revertedToolCount++
|
revertedToolCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !m.showToolDetails {
|
|
||||||
|
permission := opencode.Permission{}
|
||||||
|
if m.app.CurrentPermission.ToolCallID == part.CallID {
|
||||||
|
permission = m.app.CurrentPermission
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.showToolDetails && permission.ID == "" {
|
||||||
if !hasTextPart {
|
if !hasTextPart {
|
||||||
orphanedToolCalls = append(orphanedToolCalls, part)
|
orphanedToolCalls = append(orphanedToolCalls, part)
|
||||||
}
|
}
|
||||||
@@ -477,12 +485,14 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
part.ID,
|
part.ID,
|
||||||
m.showToolDetails,
|
m.showToolDetails,
|
||||||
width,
|
width,
|
||||||
|
permission.ID,
|
||||||
)
|
)
|
||||||
content, cached = m.cache.Get(key)
|
content, cached = m.cache.Get(key)
|
||||||
if !cached {
|
if !cached {
|
||||||
content = renderToolDetails(
|
content = renderToolDetails(
|
||||||
m.app,
|
m.app,
|
||||||
part,
|
part,
|
||||||
|
permission,
|
||||||
width,
|
width,
|
||||||
)
|
)
|
||||||
content = lipgloss.PlaceHorizontal(
|
content = lipgloss.PlaceHorizontal(
|
||||||
@@ -498,6 +508,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
content = renderToolDetails(
|
content = renderToolDetails(
|
||||||
m.app,
|
m.app,
|
||||||
part,
|
part,
|
||||||
|
permission,
|
||||||
width,
|
width,
|
||||||
)
|
)
|
||||||
content = lipgloss.PlaceHorizontal(
|
content = lipgloss.PlaceHorizontal(
|
||||||
@@ -618,6 +629,40 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
|||||||
blocks = append(blocks, content)
|
blocks = append(blocks, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if m.app.CurrentPermission.ID != "" &&
|
||||||
|
m.app.CurrentPermission.SessionID != m.app.Session.ID {
|
||||||
|
response, err := m.app.Client.Session.Message(
|
||||||
|
context.Background(),
|
||||||
|
m.app.CurrentPermission.SessionID,
|
||||||
|
m.app.CurrentPermission.MessageID,
|
||||||
|
)
|
||||||
|
if err != nil || response == nil {
|
||||||
|
slog.Error("Failed to get message from child session", "error", err)
|
||||||
|
} else {
|
||||||
|
for _, part := range response.Parts {
|
||||||
|
if part.CallID == m.app.CurrentPermission.ToolCallID {
|
||||||
|
content := renderToolDetails(
|
||||||
|
m.app,
|
||||||
|
part.AsUnion().(opencode.ToolPart),
|
||||||
|
m.app.CurrentPermission,
|
||||||
|
width,
|
||||||
|
)
|
||||||
|
content = lipgloss.PlaceHorizontal(
|
||||||
|
m.width,
|
||||||
|
lipgloss.Center,
|
||||||
|
content,
|
||||||
|
styles.WhitespaceStyle(t.Background()),
|
||||||
|
)
|
||||||
|
if content != "" {
|
||||||
|
partCount++
|
||||||
|
lineCount += lipgloss.Height(content) + 1
|
||||||
|
blocks = append(blocks, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final := []string{}
|
final := []string{}
|
||||||
clipboard := []string{}
|
clipboard := []string{}
|
||||||
var selection *selection
|
var selection *selection
|
||||||
@@ -846,9 +891,7 @@ func (m *messagesComponent) View() string {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
measure := util.Measure("messages.View")
|
|
||||||
viewport := m.viewport.View()
|
viewport := m.viewport.View()
|
||||||
measure()
|
|
||||||
return styles.NewStyle().
|
return styles.NewStyle().
|
||||||
Background(t.Background()).
|
Background(t.Background()).
|
||||||
Render(m.header + "\n" + viewport)
|
Render(m.header + "\n" + viewport)
|
||||||
|
|||||||
@@ -138,8 +138,6 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
case "n":
|
case "n":
|
||||||
s.app.Session = &opencode.Session{}
|
|
||||||
s.app.Messages = []app.Message{}
|
|
||||||
return s, tea.Sequence(
|
return s, tea.Sequence(
|
||||||
util.CmdHandler(modal.CloseModalMsg{}),
|
util.CmdHandler(modal.CloseModalMsg{}),
|
||||||
util.CmdHandler(app.SessionClearedMsg{}),
|
util.CmdHandler(app.SessionClearedMsg{}),
|
||||||
|
|||||||
@@ -103,9 +103,6 @@ func (a Model) Init() tea.Cmd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
measure := util.Measure("app.Update")
|
|
||||||
defer measure("from", fmt.Sprintf("%T", msg))
|
|
||||||
|
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
|
|
||||||
@@ -113,6 +110,45 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case tea.KeyPressMsg:
|
case tea.KeyPressMsg:
|
||||||
keyString := msg.String()
|
keyString := msg.String()
|
||||||
|
|
||||||
|
if a.app.CurrentPermission.ID != "" {
|
||||||
|
if keyString == "enter" || keyString == "esc" || keyString == "a" {
|
||||||
|
sessionID := a.app.CurrentPermission.SessionID
|
||||||
|
permissionID := a.app.CurrentPermission.ID
|
||||||
|
a.editor.Focus()
|
||||||
|
a.app.Permissions = a.app.Permissions[1:]
|
||||||
|
if len(a.app.Permissions) > 0 {
|
||||||
|
a.app.CurrentPermission = a.app.Permissions[0]
|
||||||
|
} else {
|
||||||
|
a.app.CurrentPermission = opencode.Permission{}
|
||||||
|
}
|
||||||
|
|
||||||
|
response := opencode.SessionPermissionRespondParamsResponseOnce
|
||||||
|
switch keyString {
|
||||||
|
case "enter":
|
||||||
|
response = opencode.SessionPermissionRespondParamsResponseOnce
|
||||||
|
case "a":
|
||||||
|
response = opencode.SessionPermissionRespondParamsResponseAlways
|
||||||
|
case "esc":
|
||||||
|
response = opencode.SessionPermissionRespondParamsResponseReject
|
||||||
|
}
|
||||||
|
|
||||||
|
return a, func() tea.Msg {
|
||||||
|
resp, err := a.app.Client.Session.Permissions.Respond(
|
||||||
|
context.Background(),
|
||||||
|
sessionID,
|
||||||
|
permissionID,
|
||||||
|
opencode.SessionPermissionRespondParams{Response: opencode.F(response)},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to respond to permission request", "error", err)
|
||||||
|
return toast.NewErrorToast("Failed to respond to permission request")
|
||||||
|
}
|
||||||
|
slog.Debug("Responded to permission request", "response", resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Handle active modal
|
// 1. Handle active modal
|
||||||
if a.modal != nil {
|
if a.modal != nil {
|
||||||
switch keyString {
|
switch keyString {
|
||||||
@@ -341,6 +377,9 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
updated, cmd := a.editor.Focus()
|
updated, cmd := a.editor.Focus()
|
||||||
a.editor = updated.(chat.EditorComponent)
|
a.editor = updated.(chat.EditorComponent)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
case app.SessionClearedMsg:
|
||||||
|
a.app.Session = &opencode.Session{}
|
||||||
|
a.app.Messages = []app.Message{}
|
||||||
case dialog.CompletionDialogCloseMsg:
|
case dialog.CompletionDialogCloseMsg:
|
||||||
a.showCompletionDialog = false
|
a.showCompletionDialog = false
|
||||||
case opencode.EventListResponseEventInstallationUpdated:
|
case opencode.EventListResponseEventInstallationUpdated:
|
||||||
@@ -364,7 +403,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
a.app.Session = &msg.Properties.Info
|
a.app.Session = &msg.Properties.Info
|
||||||
}
|
}
|
||||||
case opencode.EventListResponseEventMessagePartUpdated:
|
case opencode.EventListResponseEventMessagePartUpdated:
|
||||||
slog.Info("message part updated", "message", msg.Properties.Part.MessageID, "part", msg.Properties.Part.ID)
|
slog.Debug("message part updated", "message", msg.Properties.Part.MessageID, "part", msg.Properties.Part.ID)
|
||||||
if msg.Properties.Part.SessionID == a.app.Session.ID {
|
if msg.Properties.Part.SessionID == a.app.Session.ID {
|
||||||
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
|
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
|
||||||
switch casted := m.Info.(type) {
|
switch casted := m.Info.(type) {
|
||||||
@@ -402,7 +441,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case opencode.EventListResponseEventMessagePartRemoved:
|
case opencode.EventListResponseEventMessagePartRemoved:
|
||||||
slog.Info("message part removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID, "part", msg.Properties.PartID)
|
slog.Debug("message part removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID, "part", msg.Properties.PartID)
|
||||||
if msg.Properties.SessionID == a.app.Session.ID {
|
if msg.Properties.SessionID == a.app.Session.ID {
|
||||||
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
|
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
|
||||||
switch casted := m.Info.(type) {
|
switch casted := m.Info.(type) {
|
||||||
@@ -438,7 +477,7 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case opencode.EventListResponseEventMessageRemoved:
|
case opencode.EventListResponseEventMessageRemoved:
|
||||||
slog.Info("message removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID)
|
slog.Debug("message removed", "session", msg.Properties.SessionID, "message", msg.Properties.MessageID)
|
||||||
if msg.Properties.SessionID == a.app.Session.ID {
|
if msg.Properties.SessionID == a.app.Session.ID {
|
||||||
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
|
messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
|
||||||
switch casted := m.Info.(type) {
|
switch casted := m.Info.(type) {
|
||||||
@@ -480,6 +519,12 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case opencode.EventListResponseEventPermissionUpdated:
|
||||||
|
slog.Debug("permission updated", "session", msg.Properties.SessionID, "permission", msg.Properties.ID)
|
||||||
|
a.app.Permissions = append(a.app.Permissions, msg.Properties)
|
||||||
|
a.app.CurrentPermission = a.app.Permissions[0]
|
||||||
|
cmds = append(cmds, toast.NewInfoToast(msg.Properties.Title, toast.WithTitle("Permission requested")))
|
||||||
|
a.editor.Blur()
|
||||||
case opencode.EventListResponseEventSessionError:
|
case opencode.EventListResponseEventSessionError:
|
||||||
switch err := msg.Properties.Error.AsUnion().(type) {
|
switch err := msg.Properties.Error.AsUnion().(type) {
|
||||||
case nil:
|
case nil:
|
||||||
@@ -613,8 +658,6 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a Model) View() string {
|
func (a Model) View() string {
|
||||||
measure := util.Measure("app.View")
|
|
||||||
defer measure()
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
|
|
||||||
var mainLayout string
|
var mainLayout string
|
||||||
@@ -674,8 +717,6 @@ func (a Model) openFile(filepath string) (tea.Model, tea.Cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a Model) home() string {
|
func (a Model) home() string {
|
||||||
measure := util.Measure("home.View")
|
|
||||||
defer measure()
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
effectiveWidth := a.width - 4
|
effectiveWidth := a.width - 4
|
||||||
baseStyle := styles.NewStyle().Background(t.Background())
|
baseStyle := styles.NewStyle().Background(t.Background())
|
||||||
@@ -796,8 +837,6 @@ func (a Model) home() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a Model) chat() string {
|
func (a Model) chat() string {
|
||||||
measure := util.Measure("chat.View")
|
|
||||||
defer measure()
|
|
||||||
effectiveWidth := a.width - 4
|
effectiveWidth := a.width - 4
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
editorView := a.editor.View()
|
editorView := a.editor.View()
|
||||||
@@ -911,9 +950,8 @@ func (a Model) executeCommand(command commands.Command) (tea.Model, tea.Cmd) {
|
|||||||
if a.app.Session.ID == "" {
|
if a.app.Session.ID == "" {
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
a.app.Session = &opencode.Session{}
|
|
||||||
a.app.Messages = []app.Message{}
|
|
||||||
cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{}))
|
cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{}))
|
||||||
|
|
||||||
case commands.SessionListCommand:
|
case commands.SessionListCommand:
|
||||||
sessionDialog := dialog.NewSessionDialog(a.app)
|
sessionDialog := dialog.NewSessionDialog(a.app)
|
||||||
a.modal = sessionDialog
|
a.modal = sessionDialog
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
configured_endpoints: 26
|
configured_endpoints: 28
|
||||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-62d8fccba4eb8dc3a80434e0849eab3352e49fb96a718bb7b6d17ed8e582b716.yml
|
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-90f0ff2a2f214a34b74f49a5909e95c31f617bd9bb881da24ab3fe664424c79d.yml
|
||||||
openapi_spec_hash: 4ff9376cf9634e91731e63fe482ea532
|
openapi_spec_hash: 5ef69219c1869f78455b0c5374f638f8
|
||||||
config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3
|
config_hash: 7707d73ebbd7ad7042ab70466b39348d
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ Response Types:
|
|||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
|
||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
|
||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
|
||||||
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>
|
||||||
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
@@ -113,6 +114,7 @@ Methods:
|
|||||||
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
|
- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
@@ -120,6 +122,16 @@ Methods:
|
|||||||
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
Response Types:
|
||||||
|
|
||||||
|
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Permission">Permission</a>
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
|
||||||
|
- <code title="post /session/{id}/permissions/{permissionID}">client.Session.Permissions.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionService.Respond">Respond</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, permissionID <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionRespondParams">SessionPermissionRespondParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
|
||||||
|
|
||||||
# Tui
|
# Tui
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ type EventListResponse struct {
|
|||||||
// [EventListResponseEventMessageRemovedProperties],
|
// [EventListResponseEventMessageRemovedProperties],
|
||||||
// [EventListResponseEventMessagePartUpdatedProperties],
|
// [EventListResponseEventMessagePartUpdatedProperties],
|
||||||
// [EventListResponseEventMessagePartRemovedProperties],
|
// [EventListResponseEventMessagePartRemovedProperties],
|
||||||
// [EventListResponseEventStorageWriteProperties],
|
// [EventListResponseEventStorageWriteProperties], [Permission],
|
||||||
// [EventListResponseEventPermissionUpdatedProperties],
|
|
||||||
// [EventListResponseEventFileEditedProperties],
|
// [EventListResponseEventFileEditedProperties],
|
||||||
// [EventListResponseEventSessionUpdatedProperties],
|
// [EventListResponseEventSessionUpdatedProperties],
|
||||||
// [EventListResponseEventSessionDeletedProperties],
|
// [EventListResponseEventSessionDeletedProperties],
|
||||||
@@ -643,7 +642,7 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EventListResponseEventPermissionUpdated struct {
|
type EventListResponseEventPermissionUpdated struct {
|
||||||
Properties EventListResponseEventPermissionUpdatedProperties `json:"properties,required"`
|
Properties Permission `json:"properties,required"`
|
||||||
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
|
Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
|
||||||
JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
|
JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
|
||||||
}
|
}
|
||||||
@@ -667,56 +666,6 @@ func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string {
|
|||||||
|
|
||||||
func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {}
|
func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {}
|
||||||
|
|
||||||
type EventListResponseEventPermissionUpdatedProperties struct {
|
|
||||||
ID string `json:"id,required"`
|
|
||||||
Metadata map[string]interface{} `json:"metadata,required"`
|
|
||||||
SessionID string `json:"sessionID,required"`
|
|
||||||
Time EventListResponseEventPermissionUpdatedPropertiesTime `json:"time,required"`
|
|
||||||
Title string `json:"title,required"`
|
|
||||||
JSON eventListResponseEventPermissionUpdatedPropertiesJSON `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventListResponseEventPermissionUpdatedPropertiesJSON contains the JSON metadata
|
|
||||||
// for the struct [EventListResponseEventPermissionUpdatedProperties]
|
|
||||||
type eventListResponseEventPermissionUpdatedPropertiesJSON struct {
|
|
||||||
ID apijson.Field
|
|
||||||
Metadata apijson.Field
|
|
||||||
SessionID apijson.Field
|
|
||||||
Time apijson.Field
|
|
||||||
Title apijson.Field
|
|
||||||
raw string
|
|
||||||
ExtraFields map[string]apijson.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EventListResponseEventPermissionUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
return apijson.UnmarshalRoot(data, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r eventListResponseEventPermissionUpdatedPropertiesJSON) RawJSON() string {
|
|
||||||
return r.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventListResponseEventPermissionUpdatedPropertiesTime struct {
|
|
||||||
Created float64 `json:"created,required"`
|
|
||||||
JSON eventListResponseEventPermissionUpdatedPropertiesTimeJSON `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventListResponseEventPermissionUpdatedPropertiesTimeJSON contains the JSON
|
|
||||||
// metadata for the struct [EventListResponseEventPermissionUpdatedPropertiesTime]
|
|
||||||
type eventListResponseEventPermissionUpdatedPropertiesTimeJSON struct {
|
|
||||||
Created apijson.Field
|
|
||||||
raw string
|
|
||||||
ExtraFields map[string]apijson.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EventListResponseEventPermissionUpdatedPropertiesTime) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
return apijson.UnmarshalRoot(data, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r eventListResponseEventPermissionUpdatedPropertiesTimeJSON) RawJSON() string {
|
|
||||||
return r.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventListResponseEventPermissionUpdatedType string
|
type EventListResponseEventPermissionUpdatedType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
// the [NewSessionService] method instead.
|
// the [NewSessionService] method instead.
|
||||||
type SessionService struct {
|
type SessionService struct {
|
||||||
Options []option.RequestOption
|
Options []option.RequestOption
|
||||||
|
Permissions *SessionPermissionService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSessionService generates a new service that applies the given options to each
|
// NewSessionService generates a new service that applies the given options to each
|
||||||
@@ -33,6 +34,7 @@ type SessionService struct {
|
|||||||
func NewSessionService(opts ...option.RequestOption) (r *SessionService) {
|
func NewSessionService(opts ...option.RequestOption) (r *SessionService) {
|
||||||
r = &SessionService{}
|
r = &SessionService{}
|
||||||
r.Options = opts
|
r.Options = opts
|
||||||
|
r.Permissions = NewSessionPermissionService(opts...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,6 +102,22 @@ func (r *SessionService) Init(ctx context.Context, id string, body SessionInitPa
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get a message from a session
|
||||||
|
func (r *SessionService) Message(ctx context.Context, id string, messageID string, opts ...option.RequestOption) (res *SessionMessageResponse, err error) {
|
||||||
|
opts = append(r.Options[:], opts...)
|
||||||
|
if id == "" {
|
||||||
|
err = errors.New("missing required id parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if messageID == "" {
|
||||||
|
err = errors.New("missing required messageID parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("session/%s/message/%s", id, messageID)
|
||||||
|
err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// List messages for a session
|
// List messages for a session
|
||||||
func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
|
func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
|
||||||
opts = append(r.Options[:], opts...)
|
opts = append(r.Options[:], opts...)
|
||||||
@@ -2012,6 +2030,29 @@ func (r userMessageTimeJSON) RawJSON() string {
|
|||||||
return r.raw
|
return r.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionMessageResponse struct {
|
||||||
|
Info Message `json:"info,required"`
|
||||||
|
Parts []Part `json:"parts,required"`
|
||||||
|
JSON sessionMessageResponseJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionMessageResponseJSON contains the JSON metadata for the struct
|
||||||
|
// [SessionMessageResponse]
|
||||||
|
type sessionMessageResponseJSON struct {
|
||||||
|
Info apijson.Field
|
||||||
|
Parts apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *SessionMessageResponse) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sessionMessageResponseJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
type SessionMessagesResponse struct {
|
type SessionMessagesResponse struct {
|
||||||
Info Message `json:"info,required"`
|
Info Message `json:"info,required"`
|
||||||
Parts []Part `json:"parts,required"`
|
Parts []Part `json:"parts,required"`
|
||||||
|
|||||||
@@ -176,6 +176,32 @@ func TestSessionInit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionMessage(t *testing.T) {
|
||||||
|
t.Skip("skipped: tests are disabled for the time being")
|
||||||
|
baseURL := "http://localhost:4010"
|
||||||
|
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||||
|
baseURL = envURL
|
||||||
|
}
|
||||||
|
if !testutil.CheckTestServer(t, baseURL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := opencode.NewClient(
|
||||||
|
option.WithBaseURL(baseURL),
|
||||||
|
)
|
||||||
|
_, err := client.Session.Message(
|
||||||
|
context.TODO(),
|
||||||
|
"id",
|
||||||
|
"messageID",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
var apierr *opencode.Error
|
||||||
|
if errors.As(err, &apierr) {
|
||||||
|
t.Log(string(apierr.DumpRequest(true)))
|
||||||
|
}
|
||||||
|
t.Fatalf("err should be nil: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionMessages(t *testing.T) {
|
func TestSessionMessages(t *testing.T) {
|
||||||
t.Skip("skipped: tests are disabled for the time being")
|
t.Skip("skipped: tests are disabled for the time being")
|
||||||
baseURL := "http://localhost:4010"
|
baseURL := "http://localhost:4010"
|
||||||
|
|||||||
126
packages/tui/sdk/sessionpermission.go
Normal file
126
packages/tui/sdk/sessionpermission.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
package opencode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sst/opencode-sdk-go/internal/apijson"
|
||||||
|
"github.com/sst/opencode-sdk-go/internal/param"
|
||||||
|
"github.com/sst/opencode-sdk-go/internal/requestconfig"
|
||||||
|
"github.com/sst/opencode-sdk-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionPermissionService contains methods and other services that help with
|
||||||
|
// interacting with the opencode API.
|
||||||
|
//
|
||||||
|
// Note, unlike clients, this service does not read variables from the environment
|
||||||
|
// automatically. You should not instantiate this service directly, and instead use
|
||||||
|
// the [NewSessionPermissionService] method instead.
|
||||||
|
type SessionPermissionService struct {
|
||||||
|
Options []option.RequestOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSessionPermissionService generates a new service that applies the given
|
||||||
|
// options to each request. These options are applied after the parent client's
|
||||||
|
// options (if there is one), and before any request-specific options.
|
||||||
|
func NewSessionPermissionService(opts ...option.RequestOption) (r *SessionPermissionService) {
|
||||||
|
r = &SessionPermissionService{}
|
||||||
|
r.Options = opts
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Respond to a permission request
|
||||||
|
func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, body SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
|
||||||
|
opts = append(r.Options[:], opts...)
|
||||||
|
if id == "" {
|
||||||
|
err = errors.New("missing required id parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if permissionID == "" {
|
||||||
|
err = errors.New("missing required permissionID parameter")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("session/%s/permissions/%s", id, permissionID)
|
||||||
|
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permission struct {
|
||||||
|
ID string `json:"id,required"`
|
||||||
|
MessageID string `json:"messageID,required"`
|
||||||
|
Metadata map[string]interface{} `json:"metadata,required"`
|
||||||
|
SessionID string `json:"sessionID,required"`
|
||||||
|
Time PermissionTime `json:"time,required"`
|
||||||
|
Title string `json:"title,required"`
|
||||||
|
ToolCallID string `json:"toolCallID"`
|
||||||
|
JSON permissionJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// permissionJSON contains the JSON metadata for the struct [Permission]
|
||||||
|
type permissionJSON struct {
|
||||||
|
ID apijson.Field
|
||||||
|
MessageID apijson.Field
|
||||||
|
Metadata apijson.Field
|
||||||
|
SessionID apijson.Field
|
||||||
|
Time apijson.Field
|
||||||
|
Title apijson.Field
|
||||||
|
ToolCallID apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Permission) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r permissionJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermissionTime struct {
|
||||||
|
Created float64 `json:"created,required"`
|
||||||
|
JSON permissionTimeJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// permissionTimeJSON contains the JSON metadata for the struct [PermissionTime]
|
||||||
|
type permissionTimeJSON struct {
|
||||||
|
Created apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PermissionTime) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r permissionTimeJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionPermissionRespondParams struct {
|
||||||
|
Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r SessionPermissionRespondParams) MarshalJSON() (data []byte, err error) {
|
||||||
|
return apijson.MarshalRoot(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionPermissionRespondParamsResponse string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SessionPermissionRespondParamsResponseOnce SessionPermissionRespondParamsResponse = "once"
|
||||||
|
SessionPermissionRespondParamsResponseAlways SessionPermissionRespondParamsResponse = "always"
|
||||||
|
SessionPermissionRespondParamsResponseReject SessionPermissionRespondParamsResponse = "reject"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r SessionPermissionRespondParamsResponse) IsKnown() bool {
|
||||||
|
switch r {
|
||||||
|
case SessionPermissionRespondParamsResponseOnce, SessionPermissionRespondParamsResponseAlways, SessionPermissionRespondParamsResponseReject:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
43
packages/tui/sdk/sessionpermission_test.go
Normal file
43
packages/tui/sdk/sessionpermission_test.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||||
|
|
||||||
|
package opencode_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sst/opencode-sdk-go"
|
||||||
|
"github.com/sst/opencode-sdk-go/internal/testutil"
|
||||||
|
"github.com/sst/opencode-sdk-go/option"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSessionPermissionRespond(t *testing.T) {
|
||||||
|
t.Skip("skipped: tests are disabled for the time being")
|
||||||
|
baseURL := "http://localhost:4010"
|
||||||
|
if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
|
||||||
|
baseURL = envURL
|
||||||
|
}
|
||||||
|
if !testutil.CheckTestServer(t, baseURL) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
client := opencode.NewClient(
|
||||||
|
option.WithBaseURL(baseURL),
|
||||||
|
)
|
||||||
|
_, err := client.Session.Permissions.Respond(
|
||||||
|
context.TODO(),
|
||||||
|
"id",
|
||||||
|
"permissionID",
|
||||||
|
opencode.SessionPermissionRespondParams{
|
||||||
|
Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
var apierr *opencode.Error
|
||||||
|
if errors.As(err, &apierr) {
|
||||||
|
t.Log(string(apierr.DumpRequest(true)))
|
||||||
|
}
|
||||||
|
t.Fatalf("err should be nil: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user