subagent
This commit is contained in:
@@ -24,6 +24,7 @@ import { AuthAnthropic } from "../auth/anthropic"
|
|||||||
import { ModelsDev } from "./models"
|
import { ModelsDev } from "./models"
|
||||||
import { NamedError } from "../util/error"
|
import { NamedError } from "../util/error"
|
||||||
import { Auth } from "../auth"
|
import { Auth } from "../auth"
|
||||||
|
import { TaskTool } from "../tool/task"
|
||||||
|
|
||||||
export namespace Provider {
|
export namespace Provider {
|
||||||
const log = Log.create({ service: "provider" })
|
const log = Log.create({ service: "provider" })
|
||||||
@@ -298,6 +299,7 @@ export namespace Provider {
|
|||||||
// MultiEditTool,
|
// MultiEditTool,
|
||||||
WriteTool,
|
WriteTool,
|
||||||
TodoWriteTool,
|
TodoWriteTool,
|
||||||
|
TaskTool,
|
||||||
TodoReadTool,
|
TodoReadTool,
|
||||||
]
|
]
|
||||||
const TOOL_MAPPING: Record<string, Tool.Info[]> = {
|
const TOOL_MAPPING: Record<string, Tool.Info[]> = {
|
||||||
|
|||||||
@@ -12,24 +12,21 @@ import {
|
|||||||
tool,
|
tool,
|
||||||
type Tool as AITool,
|
type Tool as AITool,
|
||||||
type LanguageModelUsage,
|
type LanguageModelUsage,
|
||||||
|
type UIMessage,
|
||||||
} from "ai"
|
} from "ai"
|
||||||
import { z, ZodSchema } from "zod"
|
import { z, ZodSchema } from "zod"
|
||||||
import { Decimal } from "decimal.js"
|
import { Decimal } from "decimal.js"
|
||||||
|
|
||||||
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
|
|
||||||
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
|
|
||||||
import PROMPT_TITLE from "./prompt/title.txt"
|
|
||||||
import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
|
|
||||||
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
|
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
|
||||||
|
|
||||||
import { Share } from "../share/share"
|
import { Share } from "../share/share"
|
||||||
import { Message } from "./message"
|
import { Message } from "./message"
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
import { SessionContext } from "./context"
|
|
||||||
import { ListTool } from "../tool/ls"
|
|
||||||
import { MCP } from "../mcp"
|
import { MCP } from "../mcp"
|
||||||
import { NamedError } from "../util/error"
|
import { NamedError } from "../util/error"
|
||||||
|
import type { Tool } from "../tool/tool"
|
||||||
|
import { SystemPrompt } from "./system"
|
||||||
|
|
||||||
export namespace Session {
|
export namespace Session {
|
||||||
const log = Log.create({ service: "session" })
|
const log = Log.create({ service: "session" })
|
||||||
@@ -37,6 +34,7 @@ export namespace Session {
|
|||||||
export const Info = z
|
export const Info = z
|
||||||
.object({
|
.object({
|
||||||
id: Identifier.schema("session"),
|
id: Identifier.schema("session"),
|
||||||
|
parentID: Identifier.schema("session").optional(),
|
||||||
share: z
|
share: z
|
||||||
.object({
|
.object({
|
||||||
secret: z.string(),
|
secret: z.string(),
|
||||||
@@ -79,10 +77,11 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function create() {
|
export async function create(parentID?: string) {
|
||||||
const result: Info = {
|
const result: Info = {
|
||||||
id: Identifier.descending("session"),
|
id: Identifier.descending("session"),
|
||||||
title: "New Session - " + new Date().toISOString(),
|
parentID,
|
||||||
|
title: "Child Session - " + new Date().toISOString(),
|
||||||
time: {
|
time: {
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
updated: Date.now(),
|
updated: Date.now(),
|
||||||
@@ -91,6 +90,7 @@ export namespace Session {
|
|||||||
log.info("created", result)
|
log.info("created", result)
|
||||||
state().sessions.set(result.id, result)
|
state().sessions.set(result.id, result)
|
||||||
await Storage.writeJSON("session/info/" + result.id, result)
|
await Storage.writeJSON("session/info/" + result.id, result)
|
||||||
|
if (!result.parentID)
|
||||||
share(result.id).then((share) => {
|
share(result.id).then((share) => {
|
||||||
update(result.id, (draft) => {
|
update(result.id, (draft) => {
|
||||||
draft.share = share
|
draft.share = share
|
||||||
@@ -186,12 +186,16 @@ export namespace Session {
|
|||||||
providerID: string
|
providerID: string
|
||||||
modelID: string
|
modelID: string
|
||||||
parts: Message.Part[]
|
parts: Message.Part[]
|
||||||
|
system?: string[]
|
||||||
|
tools?: Tool.Info[]
|
||||||
}) {
|
}) {
|
||||||
const l = log.clone().tag("session", input.sessionID)
|
const l = log.clone().tag("session", input.sessionID)
|
||||||
l.info("chatting")
|
l.info("chatting")
|
||||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||||
let msgs = await messages(input.sessionID)
|
let msgs = await messages(input.sessionID)
|
||||||
const previous = msgs.at(-1)
|
const previous = msgs.at(-1)
|
||||||
|
|
||||||
|
// auto summarize if too long
|
||||||
if (previous?.metadata.assistant) {
|
if (previous?.metadata.assistant) {
|
||||||
const tokens =
|
const tokens =
|
||||||
previous.metadata.assistant.tokens.input +
|
previous.metadata.assistant.tokens.input +
|
||||||
@@ -214,95 +218,25 @@ export namespace Session {
|
|||||||
const lastSummary = msgs.findLast(
|
const lastSummary = msgs.findLast(
|
||||||
(msg) => msg.metadata.assistant?.summary === true,
|
(msg) => msg.metadata.assistant?.summary === true,
|
||||||
)
|
)
|
||||||
if (lastSummary)
|
if (lastSummary) msgs = msgs.filter((msg) => msg.id >= lastSummary.id)
|
||||||
msgs = msgs.filter(
|
|
||||||
(msg) => msg.role === "system" || msg.id >= lastSummary.id,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (msgs.length === 0) {
|
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
if (input.providerID === "anthropic") {
|
if (msgs.length === 0) {
|
||||||
const claude: Message.Info = {
|
|
||||||
id: Identifier.ascending("message"),
|
|
||||||
role: "system",
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: PROMPT_ANTHROPIC_SPOOF.trim(),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
metadata: {
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
time: {
|
|
||||||
created: Date.now(),
|
|
||||||
},
|
|
||||||
tool: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
await updateMessage(claude)
|
|
||||||
msgs.push(claude)
|
|
||||||
}
|
|
||||||
const system: Message.Info = {
|
|
||||||
id: Identifier.ascending("message"),
|
|
||||||
role: "system",
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: PROMPT_ANTHROPIC,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: [
|
|
||||||
`Here is some useful information about the environment you are running in:`,
|
|
||||||
`<env>`,
|
|
||||||
`Working directory: ${app.path.cwd}`,
|
|
||||||
`Is directory a git repo: ${app.git ? "yes" : "no"}`,
|
|
||||||
`Platform: ${process.platform}`,
|
|
||||||
`Today's date: ${new Date().toISOString()}`,
|
|
||||||
`</env>`,
|
|
||||||
`<project>`,
|
|
||||||
`${app.git ? await ListTool.execute({ path: app.path.cwd, ignore: [] }, { sessionID: input.sessionID, abort: abort.signal }).then((x) => x.output) : ""}`,
|
|
||||||
`</project>`,
|
|
||||||
].join("\n"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
metadata: {
|
|
||||||
sessionID: input.sessionID,
|
|
||||||
time: {
|
|
||||||
created: Date.now(),
|
|
||||||
},
|
|
||||||
tool: {},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
const context = await SessionContext.find()
|
|
||||||
if (context) {
|
|
||||||
system.parts.push({
|
|
||||||
type: "text",
|
|
||||||
text: context,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
msgs.push(system)
|
|
||||||
generateText({
|
generateText({
|
||||||
maxOutputTokens: 20,
|
maxOutputTokens: 20,
|
||||||
messages: convertToModelMessages([
|
messages: convertToModelMessages([
|
||||||
{
|
...SystemPrompt.title(input.providerID).map(
|
||||||
|
(x): UIMessage => ({
|
||||||
|
id: Identifier.ascending("message"),
|
||||||
role: "system",
|
role: "system",
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: PROMPT_ANTHROPIC_SPOOF.trim(),
|
text: x,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
}),
|
||||||
{
|
),
|
||||||
role: "system",
|
|
||||||
parts: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: PROMPT_TITLE,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
parts: input.parts,
|
parts: input.parts,
|
||||||
@@ -317,7 +251,6 @@ export namespace Session {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
await updateMessage(system)
|
|
||||||
}
|
}
|
||||||
const msg: Message.Info = {
|
const msg: Message.Info = {
|
||||||
role: "user",
|
role: "user",
|
||||||
@@ -334,12 +267,21 @@ export namespace Session {
|
|||||||
await updateMessage(msg)
|
await updateMessage(msg)
|
||||||
msgs.push(msg)
|
msgs.push(msg)
|
||||||
|
|
||||||
|
const system = input.system ?? SystemPrompt.provider(input.providerID)
|
||||||
|
system.push(...(await SystemPrompt.environment(input.sessionID)))
|
||||||
|
system.push(...(await SystemPrompt.custom()))
|
||||||
|
|
||||||
const next: Message.Info = {
|
const next: Message.Info = {
|
||||||
id: Identifier.ascending("message"),
|
id: Identifier.ascending("message"),
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
parts: [],
|
parts: [],
|
||||||
metadata: {
|
metadata: {
|
||||||
assistant: {
|
assistant: {
|
||||||
|
system,
|
||||||
|
path: {
|
||||||
|
cwd: app.path.cwd,
|
||||||
|
root: app.path.root,
|
||||||
|
},
|
||||||
cost: 0,
|
cost: 0,
|
||||||
tokens: {
|
tokens: {
|
||||||
input: 0,
|
input: 0,
|
||||||
@@ -358,6 +300,7 @@ export namespace Session {
|
|||||||
}
|
}
|
||||||
await updateMessage(next)
|
await updateMessage(next)
|
||||||
const tools: Record<string, AITool> = {}
|
const tools: Record<string, AITool> = {}
|
||||||
|
|
||||||
for (const item of await Provider.tools(input.providerID)) {
|
for (const item of await Provider.tools(input.providerID)) {
|
||||||
tools[item.id.replaceAll(".", "_")] = tool({
|
tools[item.id.replaceAll(".", "_")] = tool({
|
||||||
id: item.id as any,
|
id: item.id as any,
|
||||||
@@ -369,6 +312,7 @@ export namespace Session {
|
|||||||
const result = await item.execute(args, {
|
const result = await item.execute(args, {
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
abort: abort.signal,
|
abort: abort.signal,
|
||||||
|
messageID: next.id,
|
||||||
})
|
})
|
||||||
next.metadata!.tool![opts.toolCallId] = {
|
next.metadata!.tool![opts.toolCallId] = {
|
||||||
...result.metadata,
|
...result.metadata,
|
||||||
@@ -395,6 +339,7 @@ export namespace Session {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [key, item] of Object.entries(await MCP.tools())) {
|
for (const [key, item] of Object.entries(await MCP.tools())) {
|
||||||
const execute = item.execute
|
const execute = item.execute
|
||||||
if (!execute) continue
|
if (!execute) continue
|
||||||
@@ -576,7 +521,21 @@ export namespace Session {
|
|||||||
toolCallStreaming: true,
|
toolCallStreaming: true,
|
||||||
abortSignal: abort.signal,
|
abortSignal: abort.signal,
|
||||||
stopWhen: stepCountIs(1000),
|
stopWhen: stepCountIs(1000),
|
||||||
messages: convertToModelMessages(msgs),
|
messages: convertToModelMessages([
|
||||||
|
...system.map(
|
||||||
|
(x): UIMessage => ({
|
||||||
|
id: Identifier.ascending("message"),
|
||||||
|
role: "system",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: x,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
...msgs,
|
||||||
|
]),
|
||||||
temperature: model.info.id === "codex-mini-latest" ? undefined : 0,
|
temperature: model.info.id === "codex-mini-latest" ? undefined : 0,
|
||||||
tools: {
|
tools: {
|
||||||
...(await MCP.tools()),
|
...(await MCP.tools()),
|
||||||
@@ -618,10 +577,11 @@ export namespace Session {
|
|||||||
const lastSummary = msgs.findLast(
|
const lastSummary = msgs.findLast(
|
||||||
(msg) => msg.metadata.assistant?.summary === true,
|
(msg) => msg.metadata.assistant?.summary === true,
|
||||||
)?.id
|
)?.id
|
||||||
const filtered = msgs.filter(
|
const filtered = msgs.filter((msg) => !lastSummary || msg.id >= lastSummary)
|
||||||
(msg) => msg.role !== "system" && (!lastSummary || msg.id >= lastSummary),
|
|
||||||
)
|
|
||||||
const model = await Provider.getModel(input.providerID, input.modelID)
|
const model = await Provider.getModel(input.providerID, input.modelID)
|
||||||
|
const app = App.info()
|
||||||
|
const system = SystemPrompt.summarize(input.providerID)
|
||||||
|
|
||||||
const next: Message.Info = {
|
const next: Message.Info = {
|
||||||
id: Identifier.ascending("message"),
|
id: Identifier.ascending("message"),
|
||||||
role: "assistant",
|
role: "assistant",
|
||||||
@@ -630,6 +590,11 @@ export namespace Session {
|
|||||||
tool: {},
|
tool: {},
|
||||||
sessionID: input.sessionID,
|
sessionID: input.sessionID,
|
||||||
assistant: {
|
assistant: {
|
||||||
|
system,
|
||||||
|
path: {
|
||||||
|
cwd: app.path.cwd,
|
||||||
|
root: app.path.root,
|
||||||
|
},
|
||||||
summary: true,
|
summary: true,
|
||||||
cost: 0,
|
cost: 0,
|
||||||
modelID: input.modelID,
|
modelID: input.modelID,
|
||||||
@@ -650,15 +615,18 @@ export namespace Session {
|
|||||||
abortSignal: abort.signal,
|
abortSignal: abort.signal,
|
||||||
model: model.language,
|
model: model.language,
|
||||||
messages: convertToModelMessages([
|
messages: convertToModelMessages([
|
||||||
{
|
...system.map(
|
||||||
|
(x): UIMessage => ({
|
||||||
|
id: Identifier.ascending("message"),
|
||||||
role: "system",
|
role: "system",
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
text: PROMPT_SUMMARIZE,
|
text: x,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
}),
|
||||||
|
),
|
||||||
...filtered,
|
...filtered,
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export namespace Message {
|
|||||||
export const Info = z
|
export const Info = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
role: z.enum(["system", "user", "assistant"]),
|
role: z.enum(["user", "assistant"]),
|
||||||
parts: z.array(Part),
|
parts: z.array(Part),
|
||||||
metadata: z.object({
|
metadata: z.object({
|
||||||
time: z.object({
|
time: z.object({
|
||||||
@@ -161,8 +161,13 @@ export namespace Message {
|
|||||||
),
|
),
|
||||||
assistant: z
|
assistant: z
|
||||||
.object({
|
.object({
|
||||||
|
system: z.string().array(),
|
||||||
modelID: z.string(),
|
modelID: z.string(),
|
||||||
providerID: z.string(),
|
providerID: z.string(),
|
||||||
|
path: z.object({
|
||||||
|
cwd: z.string(),
|
||||||
|
root: z.string(),
|
||||||
|
}),
|
||||||
cost: z.number(),
|
cost: z.number(),
|
||||||
summary: z.boolean().optional(),
|
summary: z.boolean().optional(),
|
||||||
tokens: z.object({
|
tokens: z.object({
|
||||||
|
|||||||
75
packages/opencode/src/session/system.ts
Normal file
75
packages/opencode/src/session/system.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { App } from "../app/app"
|
||||||
|
import { ListTool } from "../tool/ls"
|
||||||
|
import { Filesystem } from "../util/filesystem"
|
||||||
|
|
||||||
|
import PROMPT_ANTHROPIC from "./prompt/anthropic.txt"
|
||||||
|
import PROMPT_ANTHROPIC_SPOOF from "./prompt/anthropic_spoof.txt"
|
||||||
|
import PROMPT_SUMMARIZE from "./prompt/summarize.txt"
|
||||||
|
import PROMPT_TITLE from "./prompt/title.txt"
|
||||||
|
|
||||||
|
export namespace SystemPrompt {
|
||||||
|
export function provider(providerID: string) {
|
||||||
|
const result = []
|
||||||
|
switch (providerID) {
|
||||||
|
case "anthropic":
|
||||||
|
result.push(PROMPT_ANTHROPIC_SPOOF.trim())
|
||||||
|
result.push(PROMPT_ANTHROPIC)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
result.push(PROMPT_ANTHROPIC)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function environment(sessionID: string) {
|
||||||
|
const app = App.info()
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
`Here is some useful information about the environment you are running in:`,
|
||||||
|
`<env>`,
|
||||||
|
` Working directory: ${app.path.cwd}`,
|
||||||
|
` Is directory a git repo: ${app.git ? "yes" : "no"}`,
|
||||||
|
` Platform: ${process.platform}`,
|
||||||
|
` Today's date: ${new Date().toDateString()}`,
|
||||||
|
`</env>`,
|
||||||
|
`<project>`,
|
||||||
|
` ${app.git ? await ListTool.execute({ path: app.path.cwd, ignore: [] }, { sessionID: sessionID, messageID: "", abort: AbortSignal.any([]) }).then((x) => x.output) : ""}`,
|
||||||
|
`</project>`,
|
||||||
|
].join("\n"),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const CUSTOM_FILES = [
|
||||||
|
"AGENTS.md",
|
||||||
|
"CLAUDE.md",
|
||||||
|
"CONTEXT.md", // deprecated
|
||||||
|
]
|
||||||
|
export async function custom() {
|
||||||
|
const { cwd, root } = App.info().path
|
||||||
|
const found = []
|
||||||
|
for (const item of CUSTOM_FILES) {
|
||||||
|
const matches = await Filesystem.findUp(item, cwd, root)
|
||||||
|
found.push(...matches.map((x) => Bun.file(x).text()))
|
||||||
|
}
|
||||||
|
return Promise.all(found)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function summarize(providerID: string) {
|
||||||
|
switch (providerID) {
|
||||||
|
case "anthropic":
|
||||||
|
return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_SUMMARIZE]
|
||||||
|
default:
|
||||||
|
return [PROMPT_SUMMARIZE]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function title(providerID: string) {
|
||||||
|
switch (providerID) {
|
||||||
|
case "anthropic":
|
||||||
|
return [PROMPT_ANTHROPIC_SPOOF.trim(), PROMPT_TITLE]
|
||||||
|
default:
|
||||||
|
return [PROMPT_TITLE]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
packages/opencode/src/tool/task.ts
Normal file
39
packages/opencode/src/tool/task.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Tool } from "./tool"
|
||||||
|
import DESCRIPTION from "./task.txt"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { Session } from "../session"
|
||||||
|
|
||||||
|
export const TaskTool = Tool.define({
|
||||||
|
id: "opencode.task",
|
||||||
|
description: DESCRIPTION,
|
||||||
|
parameters: z.object({
|
||||||
|
description: z
|
||||||
|
.string()
|
||||||
|
.describe("A short (3-5 words) description of the task"),
|
||||||
|
prompt: z.string().describe("The task for the agent to perform"),
|
||||||
|
}),
|
||||||
|
async execute(params, ctx) {
|
||||||
|
const session = await Session.create(ctx.sessionID)
|
||||||
|
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
||||||
|
const metadata = msg.metadata.assistant!
|
||||||
|
|
||||||
|
const result = await Session.chat({
|
||||||
|
sessionID: session.id,
|
||||||
|
modelID: metadata.modelID,
|
||||||
|
providerID: metadata.providerID,
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: params.prompt,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
title: params.description,
|
||||||
|
},
|
||||||
|
output: result.parts.findLast((x) => x.type === "text")!.text,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -7,6 +7,7 @@ export namespace Tool {
|
|||||||
}
|
}
|
||||||
export type Context = {
|
export type Context = {
|
||||||
sessionID: string
|
sessionID: string
|
||||||
|
messageID: string
|
||||||
abort: AbortSignal
|
abort: AbortSignal
|
||||||
}
|
}
|
||||||
export interface Info<
|
export interface Info<
|
||||||
|
|||||||
Reference in New Issue
Block a user