mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-20 16:46:23 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95fc4e974d | ||
|
|
cb25ed8cc8 | ||
|
|
dd10815b60 | ||
|
|
a2287446ce | ||
|
|
838d747514 | ||
|
|
879766aee7 | ||
|
|
d6625397d9 | ||
|
|
ab77fb080a |
@@ -187,6 +187,28 @@ const Root = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCODE_CLI_NAME
|
||||
description: "List all available models",
|
||||
params: ServerParams,
|
||||
}),
|
||||
Spec.make("stats", {
|
||||
description: "Show shareable usage statistics",
|
||||
params: {
|
||||
...ServerParams,
|
||||
days: Flag.integer("days").pipe(Flag.withDescription("Show the last N days; 0 means today"), Flag.optional),
|
||||
year: Flag.integer("year").pipe(Flag.withDescription("Show a calendar year"), Flag.optional),
|
||||
all: Flag.boolean("all").pipe(Flag.withDescription("Show lifetime statistics"), Flag.withDefault(false)),
|
||||
project: Flag.string("project").pipe(
|
||||
Flag.withDescription('Filter by project ID, or use "." for the current project'),
|
||||
Flag.optional,
|
||||
),
|
||||
models: Flag.boolean("models").pipe(Flag.withDescription("Show model usage"), Flag.withDefault(false)),
|
||||
tools: Flag.boolean("tools").pipe(Flag.withDescription("Show tool reliability"), Flag.withDefault(false)),
|
||||
cost: Flag.boolean("cost").pipe(Flag.withDescription("Show cost and token details"), Flag.withDefault(false)),
|
||||
full: Flag.boolean("full").pipe(Flag.withDescription("Show every detailed section"), Flag.withDefault(false)),
|
||||
limit: Flag.integer("limit").pipe(
|
||||
Flag.withDescription("Number of rows in detailed sections"),
|
||||
Flag.withDefault(5),
|
||||
),
|
||||
json: Flag.boolean("json").pipe(Flag.withDescription("Output statistics as JSON"), Flag.withDefault(false)),
|
||||
},
|
||||
}),
|
||||
Spec.make("export", {
|
||||
description: "Export session data as JSON",
|
||||
params: {
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
import { OpenCode, type SessionStatsInfo } from "@opencode-ai/client"
|
||||
import { Service } from "@opencode-ai/client/effect/service"
|
||||
import { Effect, Option } from "effect"
|
||||
import { EOL } from "node:os"
|
||||
import { Commands } from "../commands"
|
||||
import { Runtime } from "../../framework/runtime"
|
||||
import { ServerConnection } from "../../services/server-connection"
|
||||
|
||||
export default Runtime.handler(
|
||||
Commands.commands.stats,
|
||||
Effect.fn("cli.stats")(function* (input) {
|
||||
const days = Option.getOrUndefined(input.days)
|
||||
const year = Option.getOrUndefined(input.year)
|
||||
const project = Option.getOrUndefined(input.project)
|
||||
if ([days !== undefined, year !== undefined, input.all].filter(Boolean).length > 1)
|
||||
yield* Effect.fail(new Error("--days, --year, and --all cannot be combined"))
|
||||
if (days !== undefined && days < 0) yield* Effect.fail(new Error("--days must be zero or greater"))
|
||||
if (year !== undefined && (year < 1970 || year > 9_999))
|
||||
yield* Effect.fail(new Error("--year must be between 1970 and 9999"))
|
||||
if (input.limit < 1) yield* Effect.fail(new Error("--limit must be greater than zero"))
|
||||
|
||||
const server = yield* ServerConnection.resolve({
|
||||
server: Option.getOrUndefined(input.server),
|
||||
standalone: input.standalone,
|
||||
})
|
||||
const client = OpenCode.make({ baseUrl: server.endpoint.url, headers: Service.headers(server.endpoint) })
|
||||
const range = statsRange({ days, year, all: input.all })
|
||||
const projectID =
|
||||
project === "."
|
||||
? yield* Effect.promise(() =>
|
||||
client.location.get({ location: { directory: process.cwd() } }).then((location) => location.project.id),
|
||||
)
|
||||
: project
|
||||
const stats = yield* Effect.promise(() =>
|
||||
client.session.stats({
|
||||
from: range.from,
|
||||
to: range.to,
|
||||
project: projectID,
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC",
|
||||
}),
|
||||
)
|
||||
const output = input.json
|
||||
? JSON.stringify(stats, null, 2)
|
||||
: renderStats(stats, {
|
||||
label: range.label,
|
||||
models: input.models || input.full,
|
||||
tools: input.tools || input.full,
|
||||
cost: input.cost || input.full,
|
||||
limit: input.limit,
|
||||
color: process.stdout.isTTY && process.env.NO_COLOR === undefined,
|
||||
})
|
||||
process.stdout.write(output + EOL)
|
||||
}),
|
||||
)
|
||||
|
||||
type RenderOptions = {
|
||||
label: string
|
||||
models: boolean
|
||||
tools: boolean
|
||||
cost: boolean
|
||||
limit: number
|
||||
color: boolean
|
||||
}
|
||||
|
||||
const colors = terminalPalette()
|
||||
|
||||
export function renderStats(stats: SessionStatsInfo, options: RenderOptions) {
|
||||
const totalTokens = tokenTotal(stats.tokens)
|
||||
const terminalTools = stats.tools.succeeded + stats.tools.failed
|
||||
const toolRate = terminalTools === 0 ? undefined : (stats.tools.succeeded / terminalTools) * 100
|
||||
const primary = `1;${colors.primary}`
|
||||
const sessionLine = [
|
||||
metricCount(stats.sessions, "session", options.color),
|
||||
stats.subagents > 0 ? metricCount(stats.subagents, "subagent", options.color) : undefined,
|
||||
]
|
||||
.filter((value) => value !== undefined)
|
||||
.join(" · ")
|
||||
const toolSummary =
|
||||
toolRate === undefined ? "no tool calls" : `${style(formatPercent(toolRate), primary, options.color)} tools`
|
||||
const details = options.models || options.tools || options.cost
|
||||
const lines = details
|
||||
? []
|
||||
: [
|
||||
`${style("opencode stats", primary, options.color)} ${style(`· ${options.label}`, "2", options.color)}`,
|
||||
"",
|
||||
...renderActivity(stats.activity, stats.range.from, stats.range.to, options.color),
|
||||
"",
|
||||
sessionLine,
|
||||
`${metricCount(stats.prompts, "prompt", options.color)} · ${metricCount(stats.steps, "step", options.color)} · ${metricCount(totalTokens, "token", options.color)}`,
|
||||
`${toolSummary} · ${metricCount(stats.activeDays, "active day", options.color)} · best streak ${style(stats.streak.toString(), primary, options.color)} day${stats.streak === 1 ? "" : "s"}`,
|
||||
"",
|
||||
style("opencode.ai", "2", options.color),
|
||||
]
|
||||
|
||||
if (options.cost) lines.push(...renderCost(stats))
|
||||
if (options.models) lines.push(...(lines.length > 0 ? [""] : []), ...renderModels(stats, options.limit))
|
||||
if (options.tools) lines.push(...(lines.length > 0 ? [""] : []), ...renderTools(stats, options.limit))
|
||||
return lines.join(EOL)
|
||||
}
|
||||
|
||||
function statsRange(input: { days?: number; year?: number; all: boolean }) {
|
||||
const now = new Date()
|
||||
const to = now.getTime() + 1
|
||||
if (input.all) return { from: undefined, to, label: "all time" }
|
||||
if (input.days !== undefined) {
|
||||
const from = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
from.setDate(from.getDate() - Math.max(0, input.days - 1))
|
||||
return {
|
||||
from: from.getTime(),
|
||||
to,
|
||||
label: input.days === 0 || input.days === 1 ? "today" : `last ${input.days} days`,
|
||||
}
|
||||
}
|
||||
const year = input.year ?? now.getFullYear()
|
||||
return {
|
||||
from: new Date(year, 0, 1).getTime(),
|
||||
to: year === now.getFullYear() ? to : new Date(year + 1, 0, 1).getTime(),
|
||||
label: year === now.getFullYear() ? `${year} so far` : year.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
function renderActivity(activity: SessionStatsInfo["activity"], from: number, to: number, color: boolean) {
|
||||
const values = new Map(activity.map((day) => [day.date, day.steps]))
|
||||
const end = new Date(to - 1)
|
||||
end.setHours(12, 0, 0, 0)
|
||||
end.setDate(end.getDate() + (7 - mondayIndex(end) - 1))
|
||||
const start = new Date(from)
|
||||
start.setHours(12, 0, 0, 0)
|
||||
start.setDate(start.getDate() - mondayIndex(start))
|
||||
const latest = new Date(end)
|
||||
latest.setDate(latest.getDate() - 52 * 7)
|
||||
if (start < latest) start.setTime(latest.getTime())
|
||||
|
||||
const active = [...values.values()].filter((value) => value > 0)
|
||||
const levels = [...new Set(active)].sort((a, b) => a - b)
|
||||
const weekStarts = Array.from({ length: Math.floor((dateOrdinal(end) - dateOrdinal(start)) / 7) + 1 }, (_, week) => {
|
||||
const date = new Date(start)
|
||||
date.setDate(date.getDate() + week * 7)
|
||||
return date
|
||||
})
|
||||
const weeks = weekStarts.map((week) =>
|
||||
Array.from({ length: 7 }, (_, day) => {
|
||||
const date = new Date(week)
|
||||
date.setDate(date.getDate() + day)
|
||||
return activityGlyph(values.get(dateKey(date)) ?? 0, levels, color)
|
||||
}),
|
||||
)
|
||||
const weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]
|
||||
return [
|
||||
style("activity", `1;${colors.primary}`, color),
|
||||
` ${style(monthLabels(weekStarts), "2", color)}`,
|
||||
...weekdays.flatMap((label, day) => [
|
||||
`${style(label, "2", color)} ${weeks.map((week) => week[day]).join("")}`,
|
||||
...(day === weekdays.length - 1 ? [] : [""]),
|
||||
]),
|
||||
"",
|
||||
` ${style("less", "2", color)} ${[0, 1, 2, 3, 4].map((level) => paintActivity(level, color)).join("")} ${style("more", "2", color)}`,
|
||||
]
|
||||
}
|
||||
|
||||
function renderCost(stats: SessionStatsInfo) {
|
||||
const input = stats.tokens.input + stats.tokens.cache.read
|
||||
const cached = input === 0 ? 0 : (stats.tokens.cache.read / input) * 100
|
||||
return [
|
||||
"COST & TOKENS",
|
||||
row("cost", `$${stats.cost.toFixed(2)}`),
|
||||
row("input", formatNumber(stats.tokens.input)),
|
||||
row("output", formatNumber(stats.tokens.output)),
|
||||
row("reasoning", formatNumber(stats.tokens.reasoning)),
|
||||
row("cache read", formatNumber(stats.tokens.cache.read)),
|
||||
row("cache write", formatNumber(stats.tokens.cache.write)),
|
||||
row("cached input", formatPercent(cached)),
|
||||
]
|
||||
}
|
||||
|
||||
function renderModels(stats: SessionStatsInfo, limit: number) {
|
||||
if (stats.models.length === 0) return ["MODELS", " no model usage"]
|
||||
return [
|
||||
"MODELS",
|
||||
tableHeader("model", "tokens", "steps", "cost"),
|
||||
...stats.models
|
||||
.slice(0, limit)
|
||||
.map((item) =>
|
||||
tableRow(
|
||||
`${item.model.providerID}/${item.model.id}${item.model.variant ? `#${item.model.variant}` : ""}`,
|
||||
formatNumber(tokenTotal(item.tokens)),
|
||||
formatNumber(item.steps),
|
||||
`$${item.cost.toFixed(2)}`,
|
||||
),
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
function renderTools(stats: SessionStatsInfo, limit: number) {
|
||||
if (stats.toolUsage.length === 0) return ["TOOL RELIABILITY", " no tool calls"]
|
||||
return [
|
||||
"TOOL RELIABILITY",
|
||||
tableHeader("tool", "calls", "error", "p50"),
|
||||
...stats.toolUsage.slice(0, limit).map((tool) => {
|
||||
const terminal = tool.succeeded + tool.failed
|
||||
return tableRow(
|
||||
tool.name,
|
||||
formatNumber(tool.calls),
|
||||
terminal === 0 ? "-" : formatPercent((tool.failed / terminal) * 100),
|
||||
tool.durationP50 === undefined ? "-" : formatDuration(tool.durationP50),
|
||||
)
|
||||
}),
|
||||
"",
|
||||
`${formatNumber(stats.tools.succeeded + stats.tools.failed)} terminal calls · ${formatNumber(stats.tools.unfinished)} unfinished`,
|
||||
]
|
||||
}
|
||||
|
||||
function row(label: string, value: string) {
|
||||
return ` ${label.padEnd(20)}${value}`
|
||||
}
|
||||
|
||||
function tableHeader(label: string, second: string, third: string, fourth: string) {
|
||||
return tableRow(label, second, third, fourth)
|
||||
}
|
||||
|
||||
function tableRow(label: string, second: string, third: string, fourth: string) {
|
||||
return `${truncate(label, 34).padEnd(34)}${second.padStart(10)}${third.padStart(12)}${fourth.padStart(12)}`
|
||||
}
|
||||
|
||||
function truncate(value: string, width: number) {
|
||||
return value.length <= width ? value : value.slice(0, width - 1) + "…"
|
||||
}
|
||||
|
||||
function tokenTotal(tokens: SessionStatsInfo["tokens"]) {
|
||||
return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
|
||||
}
|
||||
|
||||
function formatNumber(value: number) {
|
||||
if (value >= 1_000_000_000) return `${trimDecimal(value / 1_000_000_000)}b`
|
||||
if (value >= 1_000_000) return `${trimDecimal(value / 1_000_000)}m`
|
||||
if (value >= 1_000) return `${trimDecimal(value / 1_000)}k`
|
||||
return Math.round(value).toLocaleString("en-US")
|
||||
}
|
||||
|
||||
function trimDecimal(value: number) {
|
||||
return value.toFixed(1).replace(/\.0$/, "")
|
||||
}
|
||||
|
||||
function formatPercent(value: number) {
|
||||
return `${value.toFixed(value >= 10 ? 1 : 2)}%`
|
||||
}
|
||||
|
||||
function formatDuration(value: number) {
|
||||
if (value < 1_000) return `${Math.round(value)}ms`
|
||||
return `${trimDecimal(value / 1_000)}s`
|
||||
}
|
||||
|
||||
function metricCount(value: number, noun: string, color: boolean) {
|
||||
return `${style(formatNumber(value), `1;${colors.primary}`, color)} ${noun}${value === 1 ? "" : "s"}`
|
||||
}
|
||||
|
||||
function style(value: string, code: string, color: boolean) {
|
||||
return color ? `\x1b[${code}m${value}\x1b[0m` : value
|
||||
}
|
||||
|
||||
function activityGlyph(value: number, levels: number[], color: boolean) {
|
||||
if (value === 0) return paintActivity(0, color)
|
||||
const index = levels.indexOf(value)
|
||||
const level = Math.max(1, Math.ceil(((index + 1) / levels.length) * 4))
|
||||
return paintActivity(level, color)
|
||||
}
|
||||
|
||||
function paintActivity(level: number, color: boolean) {
|
||||
const glyph = ["·", "░", "▒", "▓", "█"][level]
|
||||
if (!color) return glyph
|
||||
if (level === 0) return `\x1b[2m${glyph}\x1b[22m`
|
||||
return `\x1b[${colors.activity[level - 1]}m${glyph}\x1b[39m`
|
||||
}
|
||||
|
||||
function terminalPalette() {
|
||||
const background = Number(process.env.COLORFGBG?.split(";").at(-1))
|
||||
if (Number.isFinite(background) && background >= 7)
|
||||
return {
|
||||
primary: "38;2;59;125;216",
|
||||
activity: ["38;2;153;169;192", "38;2;122;155;200", "38;2;90;140;208", "38;2;59;125;216"],
|
||||
}
|
||||
return {
|
||||
primary: "38;2;250;178;131",
|
||||
activity: ["38;2;117;99;87", "38;2;161;125;102", "38;2;206;152;116", "38;2;250;178;131"],
|
||||
}
|
||||
}
|
||||
|
||||
function monthLabels(weeks: Date[]) {
|
||||
const line: string[] = []
|
||||
weeks.reduce((previous, week, index) => {
|
||||
const middle = new Date(week)
|
||||
middle.setDate(middle.getDate() + 3)
|
||||
const month = middle.getMonth()
|
||||
if (month === previous) return previous
|
||||
Intl.DateTimeFormat("en-US", { month: "short" })
|
||||
.format(middle)
|
||||
.split("")
|
||||
.forEach((character, offset) => {
|
||||
line[index + offset] = character
|
||||
})
|
||||
return month
|
||||
}, -1)
|
||||
return Array.from({ length: Math.max(weeks.length, line.length) }, (_, index) => line[index] ?? " ")
|
||||
.join("")
|
||||
.trimEnd()
|
||||
}
|
||||
|
||||
function mondayIndex(date: Date) {
|
||||
return (date.getDay() + 6) % 7
|
||||
}
|
||||
|
||||
function dateKey(date: Date) {
|
||||
return [
|
||||
date.getFullYear(),
|
||||
String(date.getMonth() + 1).padStart(2, "0"),
|
||||
String(date.getDate()).padStart(2, "0"),
|
||||
].join("-")
|
||||
}
|
||||
|
||||
function dateOrdinal(date: Date) {
|
||||
return Math.floor(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / 86_400_000)
|
||||
}
|
||||
@@ -43,6 +43,7 @@ const Handlers = Runtime.handlers(Commands, {
|
||||
remove: () => import("./commands/handlers/plugin/remove"),
|
||||
},
|
||||
models: () => import("./commands/handlers/models"),
|
||||
stats: () => import("./commands/handlers/stats"),
|
||||
export: () => import("./commands/handlers/export"),
|
||||
import: () => import("./commands/handlers/import"),
|
||||
mini: () => import("./commands/handlers/mini"),
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { SessionStatsInfo } from "@opencode-ai/client"
|
||||
import { renderStats } from "../src/commands/handlers/stats"
|
||||
|
||||
const stats: SessionStatsInfo = {
|
||||
range: { from: Date.UTC(2026, 0, 1), to: Date.UTC(2026, 0, 8) },
|
||||
sessions: 2,
|
||||
subagents: 1,
|
||||
prompts: 4,
|
||||
steps: 6,
|
||||
tokens: { input: 10_000, output: 2_000, reasoning: 1_000, cache: { read: 5_000, write: 500 } },
|
||||
cost: 12.34,
|
||||
tools: { calls: 10, succeeded: 8, failed: 2, unfinished: 0 },
|
||||
activeDays: 2,
|
||||
streak: 2,
|
||||
activity: [
|
||||
{ date: "2026-01-02", steps: 2 },
|
||||
{ date: "2026-01-03", steps: 4 },
|
||||
],
|
||||
models: [
|
||||
{
|
||||
model: { providerID: "anthropic", id: "sonnet" },
|
||||
steps: 6,
|
||||
tokens: { input: 10_000, output: 2_000, reasoning: 1_000, cache: { read: 5_000, write: 500 } },
|
||||
cost: 12.34,
|
||||
},
|
||||
],
|
||||
toolUsage: [{ name: "private_tool", calls: 10, succeeded: 8, failed: 2, unfinished: 0, durationP50: 250 }],
|
||||
}
|
||||
|
||||
describe("stats rendering", () => {
|
||||
test("keeps the default card shareable", () => {
|
||||
const output = renderStats(stats, options())
|
||||
expect(output).toContain("opencode stats · 2026 so far")
|
||||
expect(output).toContain("activity")
|
||||
expect(output).toContain("Mo ··")
|
||||
expect(output).toMatch(/Mo .*\n\nTu/)
|
||||
expect(output).toMatch(/Su .*\n\n less/)
|
||||
expect(output).toContain("less ·░▒▓█ more")
|
||||
expect(output).toContain("2 sessions · 1 subagent")
|
||||
expect(output).toContain("80.0% tools · 2 active days · best streak 2 days")
|
||||
expect(output).not.toContain("private_tool")
|
||||
expect(output).not.toContain("$12.34")
|
||||
})
|
||||
|
||||
test("renders only requested detail tables", () => {
|
||||
const output = renderStats(stats, options({ tools: true, cost: true }))
|
||||
expect(output).toContain("COST & TOKENS")
|
||||
expect(output).toContain("TOOL RELIABILITY")
|
||||
expect(output).toContain("private_tool")
|
||||
expect(output).toContain("tool")
|
||||
expect(output).toContain("calls")
|
||||
expect(output).not.toContain("opencode stats")
|
||||
expect(output).not.toContain("activity")
|
||||
})
|
||||
|
||||
test("uses the OpenCode palette in color mode", () => {
|
||||
const output = renderStats(stats, options({ color: true }))
|
||||
expect(output).toContain("\x1b[1;38;2;")
|
||||
expect(output).not.toContain("38;5;45")
|
||||
})
|
||||
})
|
||||
|
||||
function options(input: Partial<Parameters<typeof renderStats>[1]> = {}): Parameters<typeof renderStats>[1] {
|
||||
return {
|
||||
label: "2026 so far",
|
||||
models: false,
|
||||
tools: false,
|
||||
cost: false,
|
||||
limit: 5,
|
||||
color: false,
|
||||
...input,
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import type { Project } from "@opencode-ai/schema/project"
|
||||
import type { RelativePath } from "@opencode-ai/schema/schema"
|
||||
import type { Brand } from "effect"
|
||||
import type { Model } from "@opencode-ai/schema/model"
|
||||
import type { DateTime } from "effect"
|
||||
import type { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import type { SessionInbox } from "@opencode-ai/schema/session-inbox"
|
||||
import type { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||
@@ -111,64 +112,114 @@ export type Endpoint5_0Output = {
|
||||
export type SessionListOperation<E = never> = (input?: Endpoint5_0Input) => Effect.Effect<Endpoint5_0Output, E>
|
||||
|
||||
export type Endpoint5_1Input = {
|
||||
readonly from?: number | undefined
|
||||
readonly to?: number | undefined
|
||||
readonly project?: Project.ID | undefined
|
||||
readonly timezone?: string | undefined
|
||||
}
|
||||
export type Endpoint5_1Output = {
|
||||
readonly range: { readonly from: DateTime.Utc; readonly to: DateTime.Utc }
|
||||
readonly sessions: number
|
||||
readonly subagents: number
|
||||
readonly prompts: number
|
||||
readonly steps: number
|
||||
readonly tokens: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly cost: number & Brand.Brand<"Money.USD">
|
||||
readonly tools: {
|
||||
readonly calls: number
|
||||
readonly succeeded: number
|
||||
readonly failed: number
|
||||
readonly unfinished: number
|
||||
}
|
||||
readonly activeDays: number
|
||||
readonly streak: number
|
||||
readonly activity: ReadonlyArray<{ readonly date: string; readonly steps: number }>
|
||||
readonly models: ReadonlyArray<{
|
||||
readonly model: Model.Ref
|
||||
readonly steps: number
|
||||
readonly tokens: {
|
||||
readonly input: number
|
||||
readonly output: number
|
||||
readonly reasoning: number
|
||||
readonly cache: { readonly read: number; readonly write: number }
|
||||
}
|
||||
readonly cost: number & Brand.Brand<"Money.USD">
|
||||
}>
|
||||
readonly toolUsage: ReadonlyArray<{
|
||||
readonly name: string
|
||||
readonly calls: number
|
||||
readonly succeeded: number
|
||||
readonly failed: number
|
||||
readonly unfinished: number
|
||||
readonly durationP50?: number | undefined
|
||||
}>
|
||||
}
|
||||
export type SessionStatsOperation<E = never> = (input?: Endpoint5_1Input) => Effect.Effect<Endpoint5_1Output, E>
|
||||
|
||||
export type Endpoint5_2Input = {
|
||||
readonly id?: Session.ID | undefined
|
||||
readonly title?: string | undefined
|
||||
readonly agent?: Agent.ID | undefined
|
||||
readonly model?: Model.Ref | undefined
|
||||
readonly location?: Location.Ref | undefined
|
||||
}
|
||||
export type Endpoint5_1Output = Session.Info
|
||||
export type SessionCreateOperation<E = never> = (input?: Endpoint5_1Input) => Effect.Effect<Endpoint5_1Output, E>
|
||||
export type Endpoint5_2Output = Session.Info
|
||||
export type SessionCreateOperation<E = never> = (input?: Endpoint5_2Input) => Effect.Effect<Endpoint5_2Output, E>
|
||||
|
||||
export type Endpoint5_2Input = {
|
||||
export type Endpoint5_3Input = {
|
||||
readonly info: Session.Info
|
||||
readonly messages: ReadonlyArray<SessionMessage.Info>
|
||||
readonly location?: Location.Ref | undefined
|
||||
}
|
||||
export type Endpoint5_2Output = Session.Info
|
||||
export type SessionImportOperation<E = never> = (input: Endpoint5_2Input) => Effect.Effect<Endpoint5_2Output, E>
|
||||
export type Endpoint5_3Output = Session.Info
|
||||
export type SessionImportOperation<E = never> = (input: Endpoint5_3Input) => Effect.Effect<Endpoint5_3Output, E>
|
||||
|
||||
export type Endpoint5_3Input = { readonly sessionID: Session.ID; readonly sanitize?: boolean | undefined }
|
||||
export type Endpoint5_3Output = { readonly info: Session.Info; readonly messages: ReadonlyArray<SessionMessage.Info> }
|
||||
export type SessionExportOperation<E = never> = (input: Endpoint5_3Input) => Effect.Effect<Endpoint5_3Output, E>
|
||||
export type Endpoint5_4Input = { readonly sessionID: Session.ID; readonly sanitize?: boolean | undefined }
|
||||
export type Endpoint5_4Output = { readonly info: Session.Info; readonly messages: ReadonlyArray<SessionMessage.Info> }
|
||||
export type SessionExportOperation<E = never> = (input: Endpoint5_4Input) => Effect.Effect<Endpoint5_4Output, E>
|
||||
|
||||
export type Endpoint5_4Output = { readonly [x: Session.ID]: { readonly type: "running" } }
|
||||
export type SessionActiveOperation<E = never> = () => Effect.Effect<Endpoint5_4Output, E>
|
||||
|
||||
export type Endpoint5_5Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_5Output = Session.Info
|
||||
export type SessionGetOperation<E = never> = (input: Endpoint5_5Input) => Effect.Effect<Endpoint5_5Output, E>
|
||||
export type Endpoint5_5Output = { readonly [x: Session.ID]: { readonly type: "running" } }
|
||||
export type SessionActiveOperation<E = never> = () => Effect.Effect<Endpoint5_5Output, E>
|
||||
|
||||
export type Endpoint5_6Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_6Output = void
|
||||
export type SessionRemoveOperation<E = never> = (input: Endpoint5_6Input) => Effect.Effect<Endpoint5_6Output, E>
|
||||
export type Endpoint5_6Output = Session.Info
|
||||
export type SessionGetOperation<E = never> = (input: Endpoint5_6Input) => Effect.Effect<Endpoint5_6Output, E>
|
||||
|
||||
export type Endpoint5_7Input = { readonly sessionID: Session.ID; readonly boundary: Session.ForkRequestBoundary }
|
||||
export type Endpoint5_7Output = Session.Info
|
||||
export type SessionForkOperation<E = never> = (input: Endpoint5_7Input) => Effect.Effect<Endpoint5_7Output, E>
|
||||
export type Endpoint5_7Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_7Output = void
|
||||
export type SessionRemoveOperation<E = never> = (input: Endpoint5_7Input) => Effect.Effect<Endpoint5_7Output, E>
|
||||
|
||||
export type Endpoint5_8Input = { readonly sessionID: Session.ID; readonly agent: Agent.ID }
|
||||
export type Endpoint5_8Output = void
|
||||
export type SessionSwitchAgentOperation<E = never> = (input: Endpoint5_8Input) => Effect.Effect<Endpoint5_8Output, E>
|
||||
export type Endpoint5_8Input = { readonly sessionID: Session.ID; readonly boundary: Session.ForkRequestBoundary }
|
||||
export type Endpoint5_8Output = Session.Info
|
||||
export type SessionForkOperation<E = never> = (input: Endpoint5_8Input) => Effect.Effect<Endpoint5_8Output, E>
|
||||
|
||||
export type Endpoint5_9Input = { readonly sessionID: Session.ID; readonly model: Model.Ref }
|
||||
export type Endpoint5_9Input = { readonly sessionID: Session.ID; readonly agent: Agent.ID }
|
||||
export type Endpoint5_9Output = void
|
||||
export type SessionSwitchModelOperation<E = never> = (input: Endpoint5_9Input) => Effect.Effect<Endpoint5_9Output, E>
|
||||
export type SessionSwitchAgentOperation<E = never> = (input: Endpoint5_9Input) => Effect.Effect<Endpoint5_9Output, E>
|
||||
|
||||
export type Endpoint5_10Input = { readonly sessionID: Session.ID; readonly title: string }
|
||||
export type Endpoint5_10Input = { readonly sessionID: Session.ID; readonly model: Model.Ref }
|
||||
export type Endpoint5_10Output = void
|
||||
export type SessionRenameOperation<E = never> = (input: Endpoint5_10Input) => Effect.Effect<Endpoint5_10Output, E>
|
||||
export type SessionSwitchModelOperation<E = never> = (input: Endpoint5_10Input) => Effect.Effect<Endpoint5_10Output, E>
|
||||
|
||||
export type Endpoint5_11Input = {
|
||||
export type Endpoint5_11Input = { readonly sessionID: Session.ID; readonly title: string }
|
||||
export type Endpoint5_11Output = void
|
||||
export type SessionRenameOperation<E = never> = (input: Endpoint5_11Input) => Effect.Effect<Endpoint5_11Output, E>
|
||||
|
||||
export type Endpoint5_12Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly directory: AbsolutePath
|
||||
readonly workspaceID?: Workspace.ID | undefined
|
||||
readonly delivery?: SessionInbox.Delivery | undefined
|
||||
}
|
||||
export type Endpoint5_11Output = void
|
||||
export type SessionMoveOperation<E = never> = (input: Endpoint5_11Input) => Effect.Effect<Endpoint5_11Output, E>
|
||||
export type Endpoint5_12Output = void
|
||||
export type SessionMoveOperation<E = never> = (input: Endpoint5_12Input) => Effect.Effect<Endpoint5_12Output, E>
|
||||
|
||||
export type Endpoint5_12Input = {
|
||||
export type Endpoint5_13Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly id?: SessionMessage.ID | undefined
|
||||
readonly text: string
|
||||
@@ -179,10 +230,10 @@ export type Endpoint5_12Input = {
|
||||
readonly delivery?: SessionInbox.Delivery | undefined
|
||||
readonly resume?: boolean | undefined
|
||||
}
|
||||
export type Endpoint5_12Output = SessionInbox.User
|
||||
export type SessionPromptOperation<E = never> = (input: Endpoint5_12Input) => Effect.Effect<Endpoint5_12Output, E>
|
||||
export type Endpoint5_13Output = SessionInbox.User
|
||||
export type SessionPromptOperation<E = never> = (input: Endpoint5_13Input) => Effect.Effect<Endpoint5_13Output, E>
|
||||
|
||||
export type Endpoint5_13Input = {
|
||||
export type Endpoint5_14Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly id?: SessionMessage.ID | undefined
|
||||
readonly command: string
|
||||
@@ -195,19 +246,19 @@ export type Endpoint5_13Input = {
|
||||
readonly delivery?: SessionInbox.Delivery | undefined
|
||||
readonly resume?: boolean | undefined
|
||||
}
|
||||
export type Endpoint5_13Output = SessionInbox.User
|
||||
export type SessionCommandOperation<E = never> = (input: Endpoint5_13Input) => Effect.Effect<Endpoint5_13Output, E>
|
||||
export type Endpoint5_14Output = SessionInbox.User
|
||||
export type SessionCommandOperation<E = never> = (input: Endpoint5_14Input) => Effect.Effect<Endpoint5_14Output, E>
|
||||
|
||||
export type Endpoint5_14Input = {
|
||||
export type Endpoint5_15Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly id?: SessionMessage.ID | undefined
|
||||
readonly skill: Skill.ID
|
||||
readonly resume?: boolean | undefined
|
||||
}
|
||||
export type Endpoint5_14Output = void
|
||||
export type SessionSkillOperation<E = never> = (input: Endpoint5_14Input) => Effect.Effect<Endpoint5_14Output, E>
|
||||
export type Endpoint5_15Output = void
|
||||
export type SessionSkillOperation<E = never> = (input: Endpoint5_15Input) => Effect.Effect<Endpoint5_15Output, E>
|
||||
|
||||
export type Endpoint5_15Input = {
|
||||
export type Endpoint5_16Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly id?: SessionMessage.ID | undefined
|
||||
readonly text: string
|
||||
@@ -216,97 +267,97 @@ export type Endpoint5_15Input = {
|
||||
readonly delivery?: SessionInbox.Delivery | undefined
|
||||
readonly resume?: boolean | undefined
|
||||
}
|
||||
export type Endpoint5_15Output = SessionInbox.Synthetic
|
||||
export type SessionSyntheticOperation<E = never> = (input: Endpoint5_15Input) => Effect.Effect<Endpoint5_15Output, E>
|
||||
export type Endpoint5_16Output = SessionInbox.Synthetic
|
||||
export type SessionSyntheticOperation<E = never> = (input: Endpoint5_16Input) => Effect.Effect<Endpoint5_16Output, E>
|
||||
|
||||
export type Endpoint5_16Input = {
|
||||
export type Endpoint5_17Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly id?: Event.ID | undefined
|
||||
readonly command: string
|
||||
}
|
||||
export type Endpoint5_16Output = void
|
||||
export type SessionShellOperation<E = never> = (input: Endpoint5_16Input) => Effect.Effect<Endpoint5_16Output, E>
|
||||
export type Endpoint5_17Output = void
|
||||
export type SessionShellOperation<E = never> = (input: Endpoint5_17Input) => Effect.Effect<Endpoint5_17Output, E>
|
||||
|
||||
export type Endpoint5_17Input = {
|
||||
export type Endpoint5_18Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly id?: SessionMessage.ID | undefined
|
||||
readonly delivery?: SessionInbox.Delivery | undefined
|
||||
}
|
||||
export type Endpoint5_17Output = SessionInbox.Compaction
|
||||
export type SessionCompactOperation<E = never> = (input: Endpoint5_17Input) => Effect.Effect<Endpoint5_17Output, E>
|
||||
export type Endpoint5_18Output = SessionInbox.Compaction
|
||||
export type SessionCompactOperation<E = never> = (input: Endpoint5_18Input) => Effect.Effect<Endpoint5_18Output, E>
|
||||
|
||||
export type Endpoint5_18Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_18Output = void
|
||||
export type SessionWaitOperation<E = never> = (input: Endpoint5_18Input) => Effect.Effect<Endpoint5_18Output, E>
|
||||
export type Endpoint5_19Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_19Output = void
|
||||
export type SessionWaitOperation<E = never> = (input: Endpoint5_19Input) => Effect.Effect<Endpoint5_19Output, E>
|
||||
|
||||
export type Endpoint5_19Input = {
|
||||
export type Endpoint5_20Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly messageID: SessionMessage.ID
|
||||
readonly files?: boolean | undefined
|
||||
}
|
||||
export type Endpoint5_19Output = Session.Revert
|
||||
export type SessionRevertStageOperation<E = never> = (input: Endpoint5_19Input) => Effect.Effect<Endpoint5_19Output, E>
|
||||
|
||||
export type Endpoint5_20Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_20Output = void
|
||||
export type SessionRevertClearOperation<E = never> = (input: Endpoint5_20Input) => Effect.Effect<Endpoint5_20Output, E>
|
||||
export type Endpoint5_20Output = Session.Revert
|
||||
export type SessionRevertStageOperation<E = never> = (input: Endpoint5_20Input) => Effect.Effect<Endpoint5_20Output, E>
|
||||
|
||||
export type Endpoint5_21Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_21Output = void
|
||||
export type SessionRevertCommitOperation<E = never> = (input: Endpoint5_21Input) => Effect.Effect<Endpoint5_21Output, E>
|
||||
export type SessionRevertClearOperation<E = never> = (input: Endpoint5_21Input) => Effect.Effect<Endpoint5_21Output, E>
|
||||
|
||||
export type Endpoint5_22Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_22Output = ReadonlyArray<SessionMessage.Info>
|
||||
export type SessionContextOperation<E = never> = (input: Endpoint5_22Input) => Effect.Effect<Endpoint5_22Output, E>
|
||||
export type Endpoint5_22Output = void
|
||||
export type SessionRevertCommitOperation<E = never> = (input: Endpoint5_22Input) => Effect.Effect<Endpoint5_22Output, E>
|
||||
|
||||
export type Endpoint5_23Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_23Output = ReadonlyArray<SessionInbox.Info>
|
||||
export type SessionInboxListOperation<E = never> = (input: Endpoint5_23Input) => Effect.Effect<Endpoint5_23Output, E>
|
||||
export type Endpoint5_23Output = ReadonlyArray<SessionMessage.Info>
|
||||
export type SessionContextOperation<E = never> = (input: Endpoint5_23Input) => Effect.Effect<Endpoint5_23Output, E>
|
||||
|
||||
export type Endpoint5_24Input = { readonly sessionID: Session.ID; readonly inboxID: SessionMessage.ID }
|
||||
export type Endpoint5_24Output = void
|
||||
export type SessionInboxCancelOperation<E = never> = (input: Endpoint5_24Input) => Effect.Effect<Endpoint5_24Output, E>
|
||||
export type Endpoint5_24Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_24Output = ReadonlyArray<SessionInbox.Info>
|
||||
export type SessionInboxListOperation<E = never> = (input: Endpoint5_24Input) => Effect.Effect<Endpoint5_24Output, E>
|
||||
|
||||
export type Endpoint5_25Input = { readonly sessionID: Session.ID; readonly inboxID: SessionMessage.ID }
|
||||
export type Endpoint5_25Output = void
|
||||
export type SessionInboxSteerOperation<E = never> = (input: Endpoint5_25Input) => Effect.Effect<Endpoint5_25Output, E>
|
||||
export type SessionInboxCancelOperation<E = never> = (input: Endpoint5_25Input) => Effect.Effect<Endpoint5_25Output, E>
|
||||
|
||||
export type Endpoint5_26Input = { readonly sessionID: Session.ID; readonly inboxID: SessionMessage.ID }
|
||||
export type Endpoint5_26Output = void
|
||||
export type SessionInboxQueueOperation<E = never> = (input: Endpoint5_26Input) => Effect.Effect<Endpoint5_26Output, E>
|
||||
export type SessionInboxSteerOperation<E = never> = (input: Endpoint5_26Input) => Effect.Effect<Endpoint5_26Output, E>
|
||||
|
||||
export type Endpoint5_27Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_27Output = ReadonlyArray<InstructionEntry.Info>
|
||||
export type Endpoint5_27Input = { readonly sessionID: Session.ID; readonly inboxID: SessionMessage.ID }
|
||||
export type Endpoint5_27Output = void
|
||||
export type SessionInboxQueueOperation<E = never> = (input: Endpoint5_27Input) => Effect.Effect<Endpoint5_27Output, E>
|
||||
|
||||
export type Endpoint5_28Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_28Output = ReadonlyArray<InstructionEntry.Info>
|
||||
export type SessionInstructionsEntryListOperation<E = never> = (
|
||||
input: Endpoint5_27Input,
|
||||
) => Effect.Effect<Endpoint5_27Output, E>
|
||||
input: Endpoint5_28Input,
|
||||
) => Effect.Effect<Endpoint5_28Output, E>
|
||||
|
||||
export type Endpoint5_28Input = {
|
||||
export type Endpoint5_29Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly key: InstructionEntry.Key
|
||||
readonly value: Schema.Json
|
||||
}
|
||||
export type Endpoint5_28Output = void
|
||||
export type SessionInstructionsEntryPutOperation<E = never> = (
|
||||
input: Endpoint5_28Input,
|
||||
) => Effect.Effect<Endpoint5_28Output, E>
|
||||
|
||||
export type Endpoint5_29Input = { readonly sessionID: Session.ID; readonly key: InstructionEntry.Key }
|
||||
export type Endpoint5_29Output = void
|
||||
export type SessionInstructionsEntryRemoveOperation<E = never> = (
|
||||
export type SessionInstructionsEntryPutOperation<E = never> = (
|
||||
input: Endpoint5_29Input,
|
||||
) => Effect.Effect<Endpoint5_29Output, E>
|
||||
|
||||
export type Endpoint5_30Input = { readonly sessionID: Session.ID; readonly prompt: string }
|
||||
export type Endpoint5_30Output = { readonly text: string }
|
||||
export type SessionGenerateOperation<E = never> = (input: Endpoint5_30Input) => Effect.Effect<Endpoint5_30Output, E>
|
||||
export type Endpoint5_30Input = { readonly sessionID: Session.ID; readonly key: InstructionEntry.Key }
|
||||
export type Endpoint5_30Output = void
|
||||
export type SessionInstructionsEntryRemoveOperation<E = never> = (
|
||||
input: Endpoint5_30Input,
|
||||
) => Effect.Effect<Endpoint5_30Output, E>
|
||||
|
||||
export type Endpoint5_31Input = {
|
||||
export type Endpoint5_31Input = { readonly sessionID: Session.ID; readonly prompt: string }
|
||||
export type Endpoint5_31Output = { readonly text: string }
|
||||
export type SessionGenerateOperation<E = never> = (input: Endpoint5_31Input) => Effect.Effect<Endpoint5_31Output, E>
|
||||
|
||||
export type Endpoint5_32Input = {
|
||||
readonly sessionID: Session.ID
|
||||
readonly after?: Event.Seq | undefined
|
||||
readonly follow?: boolean | undefined
|
||||
}
|
||||
export type Endpoint5_31Output =
|
||||
export type Endpoint5_32Output =
|
||||
| (
|
||||
| {
|
||||
readonly id: Event.ID
|
||||
@@ -900,26 +951,27 @@ export type Endpoint5_31Output =
|
||||
}
|
||||
)
|
||||
| EventLog.Synced
|
||||
export type SessionLogOperation<E = never> = (input: Endpoint5_31Input) => Stream.Stream<Endpoint5_31Output, E>
|
||||
export type SessionLogOperation<E = never> = (input: Endpoint5_32Input) => Stream.Stream<Endpoint5_32Output, E>
|
||||
|
||||
export type Endpoint5_32Input = { readonly sessionID: Session.ID; readonly continue?: boolean | undefined }
|
||||
export type Endpoint5_32Output = void
|
||||
export type SessionInterruptOperation<E = never> = (input: Endpoint5_32Input) => Effect.Effect<Endpoint5_32Output, E>
|
||||
|
||||
export type Endpoint5_33Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_33Input = { readonly sessionID: Session.ID; readonly continue?: boolean | undefined }
|
||||
export type Endpoint5_33Output = void
|
||||
export type SessionBackgroundOperation<E = never> = (input: Endpoint5_33Input) => Effect.Effect<Endpoint5_33Output, E>
|
||||
export type SessionInterruptOperation<E = never> = (input: Endpoint5_33Input) => Effect.Effect<Endpoint5_33Output, E>
|
||||
|
||||
export type Endpoint5_34Input = { readonly sessionID: Session.ID; readonly messageID: SessionMessage.ID }
|
||||
export type Endpoint5_34Output = SessionMessage.Info
|
||||
export type SessionMessageOperation<E = never> = (input: Endpoint5_34Input) => Effect.Effect<Endpoint5_34Output, E>
|
||||
export type Endpoint5_34Input = { readonly sessionID: Session.ID }
|
||||
export type Endpoint5_34Output = void
|
||||
export type SessionBackgroundOperation<E = never> = (input: Endpoint5_34Input) => Effect.Effect<Endpoint5_34Output, E>
|
||||
|
||||
export type Endpoint5_35Input = { readonly sessionID: Session.ID; readonly variables: { readonly [x: string]: string } }
|
||||
export type Endpoint5_35Output = void
|
||||
export type SessionEnvironmentOperation<E = never> = (input: Endpoint5_35Input) => Effect.Effect<Endpoint5_35Output, E>
|
||||
export type Endpoint5_35Input = { readonly sessionID: Session.ID; readonly messageID: SessionMessage.ID }
|
||||
export type Endpoint5_35Output = SessionMessage.Info
|
||||
export type SessionMessageOperation<E = never> = (input: Endpoint5_35Input) => Effect.Effect<Endpoint5_35Output, E>
|
||||
|
||||
export type Endpoint5_36Input = { readonly sessionID: Session.ID; readonly variables: { readonly [x: string]: string } }
|
||||
export type Endpoint5_36Output = void
|
||||
export type SessionEnvironmentOperation<E = never> = (input: Endpoint5_36Input) => Effect.Effect<Endpoint5_36Output, E>
|
||||
|
||||
export interface SessionApi<E = never> {
|
||||
readonly list: SessionListOperation<E>
|
||||
readonly stats: SessionStatsOperation<E>
|
||||
readonly create: SessionCreateOperation<E>
|
||||
readonly import: SessionImportOperation<E>
|
||||
readonly export: SessionExportOperation<E>
|
||||
|
||||
@@ -23,8 +23,8 @@ import type {
|
||||
Endpoint5_2Output,
|
||||
Endpoint5_3Input,
|
||||
Endpoint5_3Output,
|
||||
Endpoint5_4Input,
|
||||
Endpoint5_4Output,
|
||||
Endpoint5_5Input,
|
||||
Endpoint5_5Output,
|
||||
Endpoint5_6Input,
|
||||
Endpoint5_6Output,
|
||||
@@ -86,6 +86,8 @@ import type {
|
||||
Endpoint5_34Output,
|
||||
Endpoint5_35Input,
|
||||
Endpoint5_35Output,
|
||||
Endpoint5_36Input,
|
||||
Endpoint5_36Output,
|
||||
Endpoint6_0Input,
|
||||
Endpoint6_0Output,
|
||||
Endpoint7_0Input,
|
||||
@@ -300,6 +302,16 @@ const Endpoint5_0 = (raw: RawClient["server.session"]) => (input?: Endpoint5_0In
|
||||
|
||||
const Endpoint5_1 = (raw: RawClient["server.session"]) => (input?: Endpoint5_1Input) =>
|
||||
preserveEffect<Endpoint5_1Output>()(
|
||||
raw["session.stats"]({
|
||||
query: { from: input?.["from"], to: input?.["to"], project: input?.["project"], timezone: input?.["timezone"] },
|
||||
}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_2 = (raw: RawClient["server.session"]) => (input?: Endpoint5_2Input) =>
|
||||
preserveEffect<Endpoint5_2Output>()(
|
||||
raw["session.create"]({
|
||||
payload: {
|
||||
id: input?.["id"],
|
||||
@@ -314,8 +326,8 @@ const Endpoint5_1 = (raw: RawClient["server.session"]) => (input?: Endpoint5_1In
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_2 = (raw: RawClient["server.session"]) => (input: Endpoint5_2Input) =>
|
||||
preserveEffect<Endpoint5_2Output>()(
|
||||
const Endpoint5_3 = (raw: RawClient["server.session"]) => (input: Endpoint5_3Input) =>
|
||||
preserveEffect<Endpoint5_3Output>()(
|
||||
raw["session.import"]({
|
||||
payload: { info: input["info"], messages: input["messages"], location: input["location"] },
|
||||
}).pipe(
|
||||
@@ -324,25 +336,17 @@ const Endpoint5_2 = (raw: RawClient["server.session"]) => (input: Endpoint5_2Inp
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_3 = (raw: RawClient["server.session"]) => (input: Endpoint5_3Input) =>
|
||||
preserveEffect<Endpoint5_3Output>()(
|
||||
const Endpoint5_4 = (raw: RawClient["server.session"]) => (input: Endpoint5_4Input) =>
|
||||
preserveEffect<Endpoint5_4Output>()(
|
||||
raw["session.export"]({ params: { sessionID: input["sessionID"] }, query: { sanitize: input["sanitize"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_4 = (raw: RawClient["server.session"]) => () =>
|
||||
preserveEffect<Endpoint5_4Output>()(
|
||||
raw["session.active"]({}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_5 = (raw: RawClient["server.session"]) => (input: Endpoint5_5Input) =>
|
||||
const Endpoint5_5 = (raw: RawClient["server.session"]) => () =>
|
||||
preserveEffect<Endpoint5_5Output>()(
|
||||
raw["session.get"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
raw["session.active"]({}).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
@@ -350,48 +354,56 @@ const Endpoint5_5 = (raw: RawClient["server.session"]) => (input: Endpoint5_5Inp
|
||||
|
||||
const Endpoint5_6 = (raw: RawClient["server.session"]) => (input: Endpoint5_6Input) =>
|
||||
preserveEffect<Endpoint5_6Output>()(
|
||||
raw["session.remove"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
raw["session.get"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_7 = (raw: RawClient["server.session"]) => (input: Endpoint5_7Input) =>
|
||||
preserveEffect<Endpoint5_7Output>()(
|
||||
raw["session.remove"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_8 = (raw: RawClient["server.session"]) => (input: Endpoint5_8Input) =>
|
||||
preserveEffect<Endpoint5_8Output>()(
|
||||
raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { boundary: input["boundary"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_8 = (raw: RawClient["server.session"]) => (input: Endpoint5_8Input) =>
|
||||
preserveEffect<Endpoint5_8Output>()(
|
||||
raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_9 = (raw: RawClient["server.session"]) => (input: Endpoint5_9Input) =>
|
||||
preserveEffect<Endpoint5_9Output>()(
|
||||
raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe(
|
||||
raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_10 = (raw: RawClient["server.session"]) => (input: Endpoint5_10Input) =>
|
||||
preserveEffect<Endpoint5_10Output>()(
|
||||
raw["session.rename"]({ params: { sessionID: input["sessionID"] }, payload: { title: input["title"] } }).pipe(
|
||||
raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_11 = (raw: RawClient["server.session"]) => (input: Endpoint5_11Input) =>
|
||||
preserveEffect<Endpoint5_11Output>()(
|
||||
raw["session.rename"]({ params: { sessionID: input["sessionID"] }, payload: { title: input["title"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_12 = (raw: RawClient["server.session"]) => (input: Endpoint5_12Input) =>
|
||||
preserveEffect<Endpoint5_12Output>()(
|
||||
raw["session.move"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { directory: input["directory"], workspaceID: input["workspaceID"], delivery: input["delivery"] },
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_12 = (raw: RawClient["server.session"]) => (input: Endpoint5_12Input) =>
|
||||
preserveEffect<Endpoint5_12Output>()(
|
||||
const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13Input) =>
|
||||
preserveEffect<Endpoint5_13Output>()(
|
||||
raw["session.prompt"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: {
|
||||
@@ -410,8 +422,8 @@ const Endpoint5_12 = (raw: RawClient["server.session"]) => (input: Endpoint5_12I
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13Input) =>
|
||||
preserveEffect<Endpoint5_13Output>()(
|
||||
const Endpoint5_14 = (raw: RawClient["server.session"]) => (input: Endpoint5_14Input) =>
|
||||
preserveEffect<Endpoint5_14Output>()(
|
||||
raw["session.command"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: {
|
||||
@@ -432,16 +444,16 @@ const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13I
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_14 = (raw: RawClient["server.session"]) => (input: Endpoint5_14Input) =>
|
||||
preserveEffect<Endpoint5_14Output>()(
|
||||
const Endpoint5_15 = (raw: RawClient["server.session"]) => (input: Endpoint5_15Input) =>
|
||||
preserveEffect<Endpoint5_15Output>()(
|
||||
raw["session.skill"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { id: input["id"], skill: input["skill"], resume: input["resume"] },
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_15 = (raw: RawClient["server.session"]) => (input: Endpoint5_15Input) =>
|
||||
preserveEffect<Endpoint5_15Output>()(
|
||||
const Endpoint5_16 = (raw: RawClient["server.session"]) => (input: Endpoint5_16Input) =>
|
||||
preserveEffect<Endpoint5_16Output>()(
|
||||
raw["session.synthetic"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: {
|
||||
@@ -458,16 +470,16 @@ const Endpoint5_15 = (raw: RawClient["server.session"]) => (input: Endpoint5_15I
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_16 = (raw: RawClient["server.session"]) => (input: Endpoint5_16Input) =>
|
||||
preserveEffect<Endpoint5_16Output>()(
|
||||
const Endpoint5_17 = (raw: RawClient["server.session"]) => (input: Endpoint5_17Input) =>
|
||||
preserveEffect<Endpoint5_17Output>()(
|
||||
raw["session.shell"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { id: input["id"], command: input["command"] },
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_17 = (raw: RawClient["server.session"]) => (input: Endpoint5_17Input) =>
|
||||
preserveEffect<Endpoint5_17Output>()(
|
||||
const Endpoint5_18 = (raw: RawClient["server.session"]) => (input: Endpoint5_18Input) =>
|
||||
preserveEffect<Endpoint5_18Output>()(
|
||||
raw["session.compact"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { id: input["id"], delivery: input["delivery"] },
|
||||
@@ -477,13 +489,13 @@ const Endpoint5_17 = (raw: RawClient["server.session"]) => (input: Endpoint5_17I
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_18 = (raw: RawClient["server.session"]) => (input: Endpoint5_18Input) =>
|
||||
preserveEffect<Endpoint5_18Output>()(
|
||||
const Endpoint5_19 = (raw: RawClient["server.session"]) => (input: Endpoint5_19Input) =>
|
||||
preserveEffect<Endpoint5_19Output>()(
|
||||
raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_19 = (raw: RawClient["server.session"]) => (input: Endpoint5_19Input) =>
|
||||
preserveEffect<Endpoint5_19Output>()(
|
||||
const Endpoint5_20 = (raw: RawClient["server.session"]) => (input: Endpoint5_20Input) =>
|
||||
preserveEffect<Endpoint5_20Output>()(
|
||||
raw["session.revert.stage"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { messageID: input["messageID"], files: input["files"] },
|
||||
@@ -493,27 +505,19 @@ const Endpoint5_19 = (raw: RawClient["server.session"]) => (input: Endpoint5_19I
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_20 = (raw: RawClient["server.session"]) => (input: Endpoint5_20Input) =>
|
||||
preserveEffect<Endpoint5_20Output>()(
|
||||
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_21 = (raw: RawClient["server.session"]) => (input: Endpoint5_21Input) =>
|
||||
preserveEffect<Endpoint5_21Output>()(
|
||||
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_22 = (raw: RawClient["server.session"]) => (input: Endpoint5_22Input) =>
|
||||
preserveEffect<Endpoint5_22Output>()(
|
||||
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_23 = (raw: RawClient["server.session"]) => (input: Endpoint5_23Input) =>
|
||||
preserveEffect<Endpoint5_23Output>()(
|
||||
raw["session.inbox.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
@@ -521,58 +525,66 @@ const Endpoint5_23 = (raw: RawClient["server.session"]) => (input: Endpoint5_23I
|
||||
|
||||
const Endpoint5_24 = (raw: RawClient["server.session"]) => (input: Endpoint5_24Input) =>
|
||||
preserveEffect<Endpoint5_24Output>()(
|
||||
raw["session.inbox.cancel"]({ params: { sessionID: input["sessionID"], inboxID: input["inboxID"] } }).pipe(
|
||||
raw["session.inbox.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25Input) =>
|
||||
preserveEffect<Endpoint5_25Output>()(
|
||||
raw["session.inbox.steer"]({ params: { sessionID: input["sessionID"], inboxID: input["inboxID"] } }).pipe(
|
||||
raw["session.inbox.cancel"]({ params: { sessionID: input["sessionID"], inboxID: input["inboxID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_26 = (raw: RawClient["server.session"]) => (input: Endpoint5_26Input) =>
|
||||
preserveEffect<Endpoint5_26Output>()(
|
||||
raw["session.inbox.queue"]({ params: { sessionID: input["sessionID"], inboxID: input["inboxID"] } }).pipe(
|
||||
raw["session.inbox.steer"]({ params: { sessionID: input["sessionID"], inboxID: input["inboxID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_27 = (raw: RawClient["server.session"]) => (input: Endpoint5_27Input) =>
|
||||
preserveEffect<Endpoint5_27Output>()(
|
||||
raw["session.inbox.queue"]({ params: { sessionID: input["sessionID"], inboxID: input["inboxID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_28 = (raw: RawClient["server.session"]) => (input: Endpoint5_28Input) =>
|
||||
preserveEffect<Endpoint5_28Output>()(
|
||||
raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_28 = (raw: RawClient["server.session"]) => (input: Endpoint5_28Input) =>
|
||||
preserveEffect<Endpoint5_28Output>()(
|
||||
const Endpoint5_29 = (raw: RawClient["server.session"]) => (input: Endpoint5_29Input) =>
|
||||
preserveEffect<Endpoint5_29Output>()(
|
||||
raw["session.instructions.entry.put"]({
|
||||
params: { sessionID: input["sessionID"], key: input["key"] },
|
||||
payload: { value: input["value"] },
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_29 = (raw: RawClient["server.session"]) => (input: Endpoint5_29Input) =>
|
||||
preserveEffect<Endpoint5_29Output>()(
|
||||
const Endpoint5_30 = (raw: RawClient["server.session"]) => (input: Endpoint5_30Input) =>
|
||||
preserveEffect<Endpoint5_30Output>()(
|
||||
raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_30 = (raw: RawClient["server.session"]) => (input: Endpoint5_30Input) =>
|
||||
preserveEffect<Endpoint5_30Output>()(
|
||||
const Endpoint5_31 = (raw: RawClient["server.session"]) => (input: Endpoint5_31Input) =>
|
||||
preserveEffect<Endpoint5_31Output>()(
|
||||
raw["session.generate"]({ params: { sessionID: input["sessionID"] }, payload: { prompt: input["prompt"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_31 = (raw: RawClient["server.session"]) => (input: Endpoint5_31Input) =>
|
||||
preserveStream<Endpoint5_31Output>()(
|
||||
const Endpoint5_32 = (raw: RawClient["server.session"]) => (input: Endpoint5_32Input) =>
|
||||
preserveStream<Endpoint5_32Output>()(
|
||||
Stream.unwrap(
|
||||
raw["session.log"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
@@ -584,29 +596,29 @@ const Endpoint5_31 = (raw: RawClient["server.session"]) => (input: Endpoint5_31I
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_32 = (raw: RawClient["server.session"]) => (input: Endpoint5_32Input) =>
|
||||
preserveEffect<Endpoint5_32Output>()(
|
||||
const Endpoint5_33 = (raw: RawClient["server.session"]) => (input: Endpoint5_33Input) =>
|
||||
preserveEffect<Endpoint5_33Output>()(
|
||||
raw["session.interrupt"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
query: { continue: input["continue"] },
|
||||
}).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_33 = (raw: RawClient["server.session"]) => (input: Endpoint5_33Input) =>
|
||||
preserveEffect<Endpoint5_33Output>()(
|
||||
const Endpoint5_34 = (raw: RawClient["server.session"]) => (input: Endpoint5_34Input) =>
|
||||
preserveEffect<Endpoint5_34Output>()(
|
||||
raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)),
|
||||
)
|
||||
|
||||
const Endpoint5_34 = (raw: RawClient["server.session"]) => (input: Endpoint5_34Input) =>
|
||||
preserveEffect<Endpoint5_34Output>()(
|
||||
const Endpoint5_35 = (raw: RawClient["server.session"]) => (input: Endpoint5_35Input) =>
|
||||
preserveEffect<Endpoint5_35Output>()(
|
||||
raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe(
|
||||
Effect.mapError(mapClientError),
|
||||
Effect.map((value) => value.data),
|
||||
),
|
||||
)
|
||||
|
||||
const Endpoint5_35 = (raw: RawClient["server.session"]) => (input: Endpoint5_35Input) =>
|
||||
preserveEffect<Endpoint5_35Output>()(
|
||||
const Endpoint5_36 = (raw: RawClient["server.session"]) => (input: Endpoint5_36Input) =>
|
||||
preserveEffect<Endpoint5_36Output>()(
|
||||
raw["session.environment"]({
|
||||
params: { sessionID: input["sessionID"] },
|
||||
payload: { variables: input["variables"] },
|
||||
@@ -615,34 +627,35 @@ const Endpoint5_35 = (raw: RawClient["server.session"]) => (input: Endpoint5_35I
|
||||
|
||||
const adaptGroup5 = (raw: RawClient["server.session"]) => ({
|
||||
list: Endpoint5_0(raw),
|
||||
create: Endpoint5_1(raw),
|
||||
import: Endpoint5_2(raw),
|
||||
export: Endpoint5_3(raw),
|
||||
active: Endpoint5_4(raw),
|
||||
get: Endpoint5_5(raw),
|
||||
remove: Endpoint5_6(raw),
|
||||
fork: Endpoint5_7(raw),
|
||||
switchAgent: Endpoint5_8(raw),
|
||||
switchModel: Endpoint5_9(raw),
|
||||
rename: Endpoint5_10(raw),
|
||||
move: Endpoint5_11(raw),
|
||||
prompt: Endpoint5_12(raw),
|
||||
command: Endpoint5_13(raw),
|
||||
skill: Endpoint5_14(raw),
|
||||
synthetic: Endpoint5_15(raw),
|
||||
shell: Endpoint5_16(raw),
|
||||
compact: Endpoint5_17(raw),
|
||||
wait: Endpoint5_18(raw),
|
||||
revert: { stage: Endpoint5_19(raw), clear: Endpoint5_20(raw), commit: Endpoint5_21(raw) },
|
||||
context: Endpoint5_22(raw),
|
||||
inbox: { list: Endpoint5_23(raw), cancel: Endpoint5_24(raw), steer: Endpoint5_25(raw), queue: Endpoint5_26(raw) },
|
||||
instructions: { entry: { list: Endpoint5_27(raw), put: Endpoint5_28(raw), remove: Endpoint5_29(raw) } },
|
||||
generate: Endpoint5_30(raw),
|
||||
log: Endpoint5_31(raw),
|
||||
interrupt: Endpoint5_32(raw),
|
||||
background: Endpoint5_33(raw),
|
||||
message: Endpoint5_34(raw),
|
||||
environment: Endpoint5_35(raw),
|
||||
stats: Endpoint5_1(raw),
|
||||
create: Endpoint5_2(raw),
|
||||
import: Endpoint5_3(raw),
|
||||
export: Endpoint5_4(raw),
|
||||
active: Endpoint5_5(raw),
|
||||
get: Endpoint5_6(raw),
|
||||
remove: Endpoint5_7(raw),
|
||||
fork: Endpoint5_8(raw),
|
||||
switchAgent: Endpoint5_9(raw),
|
||||
switchModel: Endpoint5_10(raw),
|
||||
rename: Endpoint5_11(raw),
|
||||
move: Endpoint5_12(raw),
|
||||
prompt: Endpoint5_13(raw),
|
||||
command: Endpoint5_14(raw),
|
||||
skill: Endpoint5_15(raw),
|
||||
synthetic: Endpoint5_16(raw),
|
||||
shell: Endpoint5_17(raw),
|
||||
compact: Endpoint5_18(raw),
|
||||
wait: Endpoint5_19(raw),
|
||||
revert: { stage: Endpoint5_20(raw), clear: Endpoint5_21(raw), commit: Endpoint5_22(raw) },
|
||||
context: Endpoint5_23(raw),
|
||||
inbox: { list: Endpoint5_24(raw), cancel: Endpoint5_25(raw), steer: Endpoint5_26(raw), queue: Endpoint5_27(raw) },
|
||||
instructions: { entry: { list: Endpoint5_28(raw), put: Endpoint5_29(raw), remove: Endpoint5_30(raw) } },
|
||||
generate: Endpoint5_31(raw),
|
||||
log: Endpoint5_32(raw),
|
||||
interrupt: Endpoint5_33(raw),
|
||||
background: Endpoint5_34(raw),
|
||||
message: Endpoint5_35(raw),
|
||||
environment: Endpoint5_36(raw),
|
||||
})
|
||||
|
||||
const Endpoint6_0 = (raw: RawClient["server.message"]) => (input: Endpoint6_0Input) =>
|
||||
|
||||
@@ -11,6 +11,8 @@ import type {
|
||||
PluginListOutput,
|
||||
SessionListInput,
|
||||
SessionListOutput,
|
||||
SessionStatsInput,
|
||||
SessionStatsOutput,
|
||||
SessionCreateInput,
|
||||
SessionCreateOutput,
|
||||
SessionImportInput,
|
||||
@@ -450,6 +452,23 @@ export function make(options: ClientOptions) {
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
stats: (input?: SessionStatsInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionStatsOutput }>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/session/stats`,
|
||||
query: {
|
||||
from: input?.["from"],
|
||||
to: input?.["to"],
|
||||
project: input?.["project"],
|
||||
timezone: input?.["timezone"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
).then((value) => value.data),
|
||||
create: (input?: SessionCreateInput, requestOptions?: RequestOptions) =>
|
||||
request<{ readonly data: SessionCreateOutput }>(
|
||||
{
|
||||
|
||||
@@ -30,6 +30,17 @@ export type FileDiffInfo = {
|
||||
status: "added" | "deleted" | "modified"
|
||||
}
|
||||
|
||||
export type SessionStatsActivity = { date: string; steps: number }
|
||||
|
||||
export type SessionStatsToolUsage = {
|
||||
name: string
|
||||
calls: number
|
||||
succeeded: number
|
||||
failed: number
|
||||
unfinished: number
|
||||
durationP50?: number
|
||||
}
|
||||
|
||||
export type PromptBase64 = string
|
||||
|
||||
export type PromptFileSource = { type: "inline" } | { type: "uri"; uri: string }
|
||||
@@ -1097,6 +1108,8 @@ export type VcsInfo = { branch: VcsBranch }
|
||||
|
||||
export type PermissionRuleset = Array<PermissionRule>
|
||||
|
||||
export type SessionStatsModelUsage = { model: ModelRef; steps: number; tokens: TokenUsageInfo; cost: MoneyUSD }
|
||||
|
||||
export type SessionStepEnded = {
|
||||
id: string
|
||||
created: number
|
||||
@@ -1648,6 +1661,22 @@ export type ConfigEntry =
|
||||
| { type: "agents"; path: string }
|
||||
| { type: "claude"; path: string }
|
||||
|
||||
export type SessionStatsInfo = {
|
||||
range: { from: number; to: number }
|
||||
sessions: number
|
||||
subagents: number
|
||||
prompts: number
|
||||
steps: number
|
||||
tokens: TokenUsageInfo
|
||||
cost: MoneyUSD
|
||||
tools: { calls: number; succeeded: number; failed: number; unfinished: number }
|
||||
activeDays: number
|
||||
streak: number
|
||||
activity: Array<SessionStatsActivity>
|
||||
models: Array<SessionStatsModelUsage>
|
||||
toolUsage: Array<SessionStatsToolUsage>
|
||||
}
|
||||
|
||||
export type SessionInfo = {
|
||||
id: string
|
||||
parentID?: string
|
||||
@@ -2438,6 +2467,35 @@ export type SessionListInput = {
|
||||
|
||||
export type SessionListOutput = SessionsResponse
|
||||
|
||||
export type SessionStatsInput = {
|
||||
readonly from?: {
|
||||
readonly from?: number | undefined
|
||||
readonly to?: number | undefined
|
||||
readonly project?: string | undefined
|
||||
readonly timezone?: string | undefined
|
||||
}["from"]
|
||||
readonly to?: {
|
||||
readonly from?: number | undefined
|
||||
readonly to?: number | undefined
|
||||
readonly project?: string | undefined
|
||||
readonly timezone?: string | undefined
|
||||
}["to"]
|
||||
readonly project?: {
|
||||
readonly from?: number | undefined
|
||||
readonly to?: number | undefined
|
||||
readonly project?: string | undefined
|
||||
readonly timezone?: string | undefined
|
||||
}["project"]
|
||||
readonly timezone?: {
|
||||
readonly from?: number | undefined
|
||||
readonly to?: number | undefined
|
||||
readonly project?: string | undefined
|
||||
readonly timezone?: string | undefined
|
||||
}["timezone"]
|
||||
}
|
||||
|
||||
export type SessionStatsOutput = { data: SessionStatsInfo }["data"]
|
||||
|
||||
export type SessionCreateInput = {
|
||||
readonly id?: {
|
||||
readonly id?: string | null
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
export * as SessionStats from "./stats.js"
|
||||
|
||||
import { DateTime, Effect, Option, Schema } from "effect"
|
||||
import { and, eq, gte, inArray, lt } from "drizzle-orm"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { Project } from "@opencode-ai/schema/project"
|
||||
import { SessionEvent } from "@opencode-ai/schema/session-event"
|
||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import { Database } from "../database/database.js"
|
||||
import { EventTable } from "../event/sql.js"
|
||||
import { SessionMessageTable, SessionTable } from "./sql.js"
|
||||
|
||||
type Input = {
|
||||
readonly from?: number
|
||||
readonly to?: number
|
||||
readonly projectID?: Project.ID
|
||||
readonly timezone?: string
|
||||
}
|
||||
|
||||
type Tokens = {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: { read: number; write: number }
|
||||
}
|
||||
|
||||
type ModelAggregate = {
|
||||
model: SessionMessage.Assistant["model"]
|
||||
steps: number
|
||||
tokens: Tokens
|
||||
cost: number
|
||||
}
|
||||
|
||||
type ToolAggregate = {
|
||||
name: string
|
||||
calls: number
|
||||
succeeded: number
|
||||
failed: number
|
||||
unfinished: number
|
||||
durations: number[]
|
||||
}
|
||||
|
||||
const decodeMessage = Schema.decodeUnknownOption(SessionMessage.Info)
|
||||
const decodeUsage = Schema.decodeUnknownOption(SessionEvent.UsageRecorded.data)
|
||||
|
||||
export const get = Effect.fn("SessionStats.get")(function* (input: Input = {}) {
|
||||
const db = (yield* Database.Service).db
|
||||
const rows = yield* db
|
||||
.select({
|
||||
id: SessionMessageTable.id,
|
||||
sessionID: SessionMessageTable.session_id,
|
||||
parentID: SessionTable.parent_id,
|
||||
type: SessionMessageTable.type,
|
||||
data: SessionMessageTable.data,
|
||||
timeCreated: SessionMessageTable.time_created,
|
||||
})
|
||||
.from(SessionMessageTable)
|
||||
.innerJoin(SessionTable, eq(SessionMessageTable.session_id, SessionTable.id))
|
||||
.where(
|
||||
and(
|
||||
inArray(SessionMessageTable.type, ["user", "assistant"]),
|
||||
input.from === undefined ? undefined : gte(SessionMessageTable.time_created, input.from),
|
||||
input.to === undefined ? undefined : lt(SessionMessageTable.time_created, input.to),
|
||||
input.projectID === undefined ? undefined : eq(SessionTable.project_id, input.projectID),
|
||||
),
|
||||
)
|
||||
.all()
|
||||
.pipe(Effect.orDie)
|
||||
const sessionIDs = [...new Set(rows.map((row) => row.sessionID))]
|
||||
const events = (yield* Effect.forEach(
|
||||
Array.from({ length: Math.ceil(sessionIDs.length / 500) }, (_, index) =>
|
||||
sessionIDs.slice(index * 500, (index + 1) * 500),
|
||||
),
|
||||
(batch) =>
|
||||
db
|
||||
.select({
|
||||
created: EventTable.created,
|
||||
data: EventTable.data,
|
||||
})
|
||||
.from(EventTable)
|
||||
.where(
|
||||
and(
|
||||
inArray(EventTable.aggregate_id, batch),
|
||||
eq(EventTable.type, SessionEvent.UsageRecorded.type),
|
||||
input.from === undefined ? undefined : gte(EventTable.created, input.from),
|
||||
input.to === undefined ? undefined : lt(EventTable.created, input.to),
|
||||
),
|
||||
)
|
||||
.all()
|
||||
.pipe(Effect.orDie),
|
||||
{ concurrency: 4 },
|
||||
)).flat()
|
||||
|
||||
const sessions = new Set<string>()
|
||||
const subagents = new Set<string>()
|
||||
const activity = new Map<string, number>()
|
||||
const models = new Map<string, ModelAggregate>()
|
||||
const tools = new Map<string, ToolAggregate>()
|
||||
const totals = {
|
||||
prompts: 0,
|
||||
steps: 0,
|
||||
tokens: emptyTokens(),
|
||||
cost: 0,
|
||||
tools: { calls: 0, succeeded: 0, failed: 0, unfinished: 0 },
|
||||
}
|
||||
const dateKey = makeDateKey(input.timezone)
|
||||
|
||||
rows.forEach((row) => {
|
||||
const decoded = decodeMessage({ ...row.data, id: row.id, type: row.type })
|
||||
if (Option.isNone(decoded)) return
|
||||
const message = decoded.value
|
||||
if (row.parentID) subagents.add(row.sessionID)
|
||||
else sessions.add(row.sessionID)
|
||||
|
||||
if (message.type === "user") {
|
||||
if (!row.parentID) totals.prompts++
|
||||
return
|
||||
}
|
||||
if (message.type !== "assistant") return
|
||||
|
||||
totals.steps++
|
||||
const tokens = message.tokens ?? emptyTokens()
|
||||
const cost = message.cost ?? 0
|
||||
addTokens(totals.tokens, tokens)
|
||||
totals.cost += cost
|
||||
const day = dateKey(DateTime.toEpochMillis(message.time.created))
|
||||
activity.set(day, (activity.get(day) ?? 0) + 1)
|
||||
|
||||
const modelKey = `${message.model.providerID}/${message.model.id}#${message.model.variant ?? ""}`
|
||||
const model = models.get(modelKey) ?? { model: message.model, steps: 0, tokens: emptyTokens(), cost: 0 }
|
||||
models.set(modelKey, model)
|
||||
model.steps++
|
||||
model.cost += cost
|
||||
addTokens(model.tokens, tokens)
|
||||
|
||||
message.content
|
||||
.filter((content): content is SessionMessage.AssistantTool => content.type === "tool")
|
||||
.forEach((content) => {
|
||||
const tool = tools.get(content.name) ?? {
|
||||
name: content.name,
|
||||
calls: 0,
|
||||
succeeded: 0,
|
||||
failed: 0,
|
||||
unfinished: 0,
|
||||
durations: [],
|
||||
}
|
||||
tools.set(content.name, tool)
|
||||
tool.calls++
|
||||
totals.tools.calls++
|
||||
if (content.state.status === "completed") {
|
||||
tool.succeeded++
|
||||
totals.tools.succeeded++
|
||||
} else if (content.state.status === "error") {
|
||||
tool.failed++
|
||||
totals.tools.failed++
|
||||
} else {
|
||||
tool.unfinished++
|
||||
totals.tools.unfinished++
|
||||
}
|
||||
if (content.time.completed === undefined) return
|
||||
tool.durations.push(
|
||||
DateTime.toEpochMillis(content.time.completed) -
|
||||
DateTime.toEpochMillis(content.time.ran ?? content.time.created),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
events.forEach((row) => {
|
||||
const decoded = decodeUsage(row.data)
|
||||
if (Option.isNone(decoded)) return
|
||||
addTokens(totals.tokens, decoded.value.tokens)
|
||||
totals.cost += decoded.value.cost
|
||||
})
|
||||
|
||||
const days = [...activity.entries()].sort(([a], [b]) => a.localeCompare(b))
|
||||
const now = Date.now()
|
||||
const fallback = input.to ?? now
|
||||
const earliestMessage = rows.reduce((earliest, row) => Math.min(earliest, row.timeCreated), fallback)
|
||||
const earliest = events.reduce((value, event) => Math.min(value, event.created), earliestMessage)
|
||||
const from = input.from ?? earliest
|
||||
const to = input.to ?? now
|
||||
|
||||
return {
|
||||
range: { from: DateTime.makeUnsafe(from), to: DateTime.makeUnsafe(to) },
|
||||
sessions: sessions.size,
|
||||
subagents: subagents.size,
|
||||
prompts: totals.prompts,
|
||||
steps: totals.steps,
|
||||
tokens: totals.tokens,
|
||||
cost: Money.USD.make(totals.cost),
|
||||
tools: totals.tools,
|
||||
activeDays: days.length,
|
||||
streak: longestStreak(days.map(([date]) => date)),
|
||||
activity: days.map(([date, steps]) => ({ date, steps })),
|
||||
models: [...models.values()]
|
||||
.sort((a, b) => tokenTotal(b.tokens) - tokenTotal(a.tokens))
|
||||
.map((model) => ({ ...model, cost: Money.USD.make(model.cost) })),
|
||||
toolUsage: [...tools.values()]
|
||||
.sort((a, b) => b.calls - a.calls)
|
||||
.map((tool) => ({
|
||||
name: tool.name,
|
||||
calls: tool.calls,
|
||||
succeeded: tool.succeeded,
|
||||
failed: tool.failed,
|
||||
unfinished: tool.unfinished,
|
||||
durationP50: median(tool.durations),
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
function emptyTokens(): Tokens {
|
||||
return { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
|
||||
}
|
||||
|
||||
function addTokens(target: Tokens, source: Tokens) {
|
||||
target.input += source.input
|
||||
target.output += source.output
|
||||
target.reasoning += source.reasoning
|
||||
target.cache.read += source.cache.read
|
||||
target.cache.write += source.cache.write
|
||||
}
|
||||
|
||||
function tokenTotal(tokens: Tokens) {
|
||||
return tokens.input + tokens.output + tokens.reasoning + tokens.cache.read + tokens.cache.write
|
||||
}
|
||||
|
||||
function makeDateKey(timezone = "UTC") {
|
||||
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone: timezone,
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
})
|
||||
return (time: number) => {
|
||||
const parts = Object.fromEntries(formatter.formatToParts(time).map((part) => [part.type, part.value]))
|
||||
return `${parts.year}-${parts.month}-${parts.day}`
|
||||
}
|
||||
}
|
||||
|
||||
function longestStreak(days: string[]) {
|
||||
return days.reduce(
|
||||
(result, day, index) => {
|
||||
const previous = days[index - 1]
|
||||
const current = previous && dayOrdinal(day) - dayOrdinal(previous) === 1 ? result.current + 1 : 1
|
||||
return { current, longest: Math.max(result.longest, current) }
|
||||
},
|
||||
{ current: 0, longest: 0 },
|
||||
).longest
|
||||
}
|
||||
|
||||
function dayOrdinal(value: string) {
|
||||
const [year, month, day] = value.split("-").map(Number)
|
||||
return Math.floor(Date.UTC(year, month - 1, day) / 86_400_000)
|
||||
}
|
||||
|
||||
function median(values: number[]) {
|
||||
if (values.length === 0) return undefined
|
||||
const sorted = values.toSorted((a, b) => a - b)
|
||||
const middle = Math.floor(sorted.length / 2)
|
||||
return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle]
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import { describe, expect } from "bun:test"
|
||||
import { Agent } from "@opencode-ai/schema/agent"
|
||||
import { Event } from "@opencode-ai/schema/event"
|
||||
import { Model } from "@opencode-ai/schema/model"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { Project } from "@opencode-ai/schema/project"
|
||||
import { Provider } from "@opencode-ai/schema/provider"
|
||||
import { Session } from "@opencode-ai/schema/session"
|
||||
import { SessionEvent } from "@opencode-ai/schema/session-event"
|
||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { EventSequenceTable, EventTable } from "@opencode-ai/core/event/sql"
|
||||
import { ProjectTable } from "@opencode-ai/core/project/sql"
|
||||
import { SessionMessageTable, SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { SessionStats } from "@opencode-ai/core/session/stats"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { DateTime, Effect, Schema } from "effect"
|
||||
import { testEffect } from "./lib/effect"
|
||||
|
||||
const it = testEffect(AppNodeBuilder.build(Database.node))
|
||||
const projectID = Project.ID.make("stats-project")
|
||||
const sessionID = Session.ID.make("ses_stats_root")
|
||||
const childID = Session.ID.make("ses_stats_child")
|
||||
const encodeMessage = Schema.encodeSync(SessionMessage.Info)
|
||||
const encodeUsage = Schema.encodeSync(SessionEvent.UsageRecorded.data)
|
||||
|
||||
describe("SessionStats", () => {
|
||||
it.effect("aggregates activity and tool reliability without reading message payloads outside the range", () =>
|
||||
Effect.gen(function* () {
|
||||
const db = (yield* Database.Service).db
|
||||
yield* db
|
||||
.insert(ProjectTable)
|
||||
.values({ id: projectID, worktree: AbsolutePath.make("/stats"), name: "stats", sandboxes: [] })
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.insert(SessionTable)
|
||||
.values([
|
||||
{ id: sessionID, project_id: projectID, slug: "root", directory: "/stats", version: "test" },
|
||||
{
|
||||
id: childID,
|
||||
project_id: projectID,
|
||||
parent_id: sessionID,
|
||||
slug: "child",
|
||||
directory: "/stats",
|
||||
version: "test",
|
||||
},
|
||||
])
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.insert(SessionMessageTable)
|
||||
.values([
|
||||
messageRow(
|
||||
sessionID,
|
||||
1,
|
||||
SessionMessage.User.make({
|
||||
id: SessionMessage.ID.make("msg_stats_user"),
|
||||
type: "user",
|
||||
text: "hello",
|
||||
time: { created: DateTime.makeUnsafe(Date.UTC(2026, 0, 2, 9)) },
|
||||
}),
|
||||
),
|
||||
messageRow(
|
||||
sessionID,
|
||||
2,
|
||||
assistant("msg_stats_assistant", Date.UTC(2026, 0, 2, 10), [
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "call_read",
|
||||
name: "read",
|
||||
state: SessionMessage.ToolStateCompleted.make({
|
||||
status: "completed",
|
||||
input: {},
|
||||
content: [{ type: "text", text: "ok" }],
|
||||
}),
|
||||
time: {
|
||||
created: DateTime.makeUnsafe(Date.UTC(2026, 0, 2, 10)),
|
||||
ran: DateTime.makeUnsafe(Date.UTC(2026, 0, 2, 10, 0, 1)),
|
||||
completed: DateTime.makeUnsafe(Date.UTC(2026, 0, 2, 10, 0, 1, 250)),
|
||||
},
|
||||
}),
|
||||
SessionMessage.AssistantTool.make({
|
||||
type: "tool",
|
||||
id: "call_edit",
|
||||
name: "edit",
|
||||
state: SessionMessage.ToolStateError.make({
|
||||
status: "error",
|
||||
input: {},
|
||||
error: { type: "tool", message: "failed" },
|
||||
}),
|
||||
time: {
|
||||
created: DateTime.makeUnsafe(Date.UTC(2026, 0, 2, 10)),
|
||||
completed: DateTime.makeUnsafe(Date.UTC(2026, 0, 2, 10, 0, 2)),
|
||||
},
|
||||
}),
|
||||
]),
|
||||
),
|
||||
messageRow(childID, 1, assistant("msg_stats_child", Date.UTC(2026, 0, 3, 10), [], "large", 2)),
|
||||
messageRow(sessionID, 3, assistant("msg_stats_outside", Date.UTC(2025, 11, 31, 10), [])),
|
||||
])
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.insert(EventSequenceTable)
|
||||
.values([
|
||||
{ aggregate_id: sessionID, seq: 0 },
|
||||
{ aggregate_id: childID, seq: 0 },
|
||||
])
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
yield* db
|
||||
.insert(EventTable)
|
||||
.values({
|
||||
id: Event.ID.make("evt_stats_usage"),
|
||||
aggregate_id: sessionID,
|
||||
seq: 0,
|
||||
created: Date.UTC(2026, 0, 2, 10, 0, 3),
|
||||
type: SessionEvent.UsageRecorded.type,
|
||||
data: encodeUsage({
|
||||
sessionID,
|
||||
source: "title",
|
||||
cost: Money.USD.make(0.5),
|
||||
tokens: { input: 1, output: 1, reasoning: 1, cache: { read: 1, write: 1 } },
|
||||
}),
|
||||
})
|
||||
.run()
|
||||
.pipe(Effect.orDie)
|
||||
|
||||
const stats = yield* SessionStats.get({
|
||||
from: Date.UTC(2026, 0, 1),
|
||||
to: Date.UTC(2026, 1, 1),
|
||||
timezone: "UTC",
|
||||
})
|
||||
|
||||
expect(stats.sessions).toBe(1)
|
||||
expect(stats.subagents).toBe(1)
|
||||
expect(stats.prompts).toBe(1)
|
||||
expect(stats.steps).toBe(2)
|
||||
expect(stats.tokens).toEqual({ input: 31, output: 16, reasoning: 7, cache: { read: 13, write: 4 } })
|
||||
expect(stats.cost).toBe(Money.USD.make(5))
|
||||
expect(stats.tools).toEqual({ calls: 2, succeeded: 1, failed: 1, unfinished: 0 })
|
||||
expect(stats.activity).toEqual([
|
||||
{ date: "2026-01-02", steps: 1 },
|
||||
{ date: "2026-01-03", steps: 1 },
|
||||
])
|
||||
expect(stats.streak).toBe(2)
|
||||
expect(stats.models.map((model) => String(model.model.id))).toEqual(["large", "sonnet"])
|
||||
expect(stats.toolUsage).toMatchObject([
|
||||
{ name: "read", calls: 1, succeeded: 1, failed: 0, durationP50: 250 },
|
||||
{ name: "edit", calls: 1, succeeded: 0, failed: 1, durationP50: 2_000 },
|
||||
])
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
function assistant(
|
||||
id: string,
|
||||
created: number,
|
||||
content: SessionMessage.AssistantContent[],
|
||||
model = "sonnet",
|
||||
scale = 1,
|
||||
) {
|
||||
return SessionMessage.Assistant.make({
|
||||
id: SessionMessage.ID.make(id),
|
||||
type: "assistant",
|
||||
agent: Agent.ID.make("build"),
|
||||
model: { id: Model.ID.make(model), providerID: Provider.ID.make("anthropic") },
|
||||
content,
|
||||
cost: Money.USD.make(1.5 * scale),
|
||||
tokens: { input: 10 * scale, output: 5 * scale, reasoning: 2 * scale, cache: { read: 4 * scale, write: scale } },
|
||||
time: { created: DateTime.makeUnsafe(created), completed: DateTime.makeUnsafe(created + 2_000) },
|
||||
})
|
||||
}
|
||||
|
||||
function messageRow(
|
||||
sessionID: Session.ID,
|
||||
seq: number,
|
||||
message: SessionMessage.Info,
|
||||
): typeof SessionMessageTable.$inferInsert {
|
||||
const encoded = encodeMessage(message)
|
||||
const { id, type, ...data } = encoded
|
||||
return { id: SessionMessage.ID.make(id), session_id: sessionID, type, seq, time_created: encoded.time.created, data }
|
||||
}
|
||||
+517
-89
@@ -362,7 +362,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Retrieve currently loaded plugins.",
|
||||
"description": "Retrieve enabled server plugins and their current status.",
|
||||
"summary": "List plugins"
|
||||
}
|
||||
},
|
||||
@@ -480,14 +480,7 @@
|
||||
"name": "project",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"$ref": "#/components/schemas/Union_2"
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
@@ -640,13 +633,13 @@
|
||||
"$ref": "#/components/schemas/Union_"
|
||||
},
|
||||
"agent": {
|
||||
"$ref": "#/components/schemas/Union_2"
|
||||
},
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Union_3"
|
||||
},
|
||||
"location": {
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Union_4"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/components/schemas/Union_5"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -657,6 +650,109 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/session/stats": {
|
||||
"get": {
|
||||
"tags": ["session"],
|
||||
"operationId": "v2.session.stats",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "from",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "to",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "project",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Union_2"
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "timezone",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Union_"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"security": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/SessionStats.Info"
|
||||
}
|
||||
},
|
||||
"required": ["data"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "InvalidRequestError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/InvalidRequestErrorEncoded"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "UnauthorizedError",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UnauthorizedErrorEncoded"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Aggregate local session activity, usage, and tool reliability for a time range.",
|
||||
"summary": "Get session statistics"
|
||||
}
|
||||
},
|
||||
"/api/session/import": {
|
||||
"post": {
|
||||
"tags": ["session"],
|
||||
@@ -727,7 +823,7 @@
|
||||
"$ref": "#/components/schemas/Arrays_"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/components/schemas/Union_4"
|
||||
"$ref": "#/components/schemas/Union_5"
|
||||
}
|
||||
},
|
||||
"required": ["info", "messages"],
|
||||
@@ -761,7 +857,7 @@
|
||||
"name": "sanitize",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
@@ -1459,7 +1555,7 @@
|
||||
]
|
||||
},
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
}
|
||||
},
|
||||
"required": ["directory"],
|
||||
@@ -1573,7 +1669,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
@@ -1591,10 +1687,10 @@
|
||||
"$ref": "#/components/schemas/Objects_3"
|
||||
},
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
},
|
||||
"resume": {
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
}
|
||||
},
|
||||
"required": ["text"],
|
||||
@@ -1721,7 +1817,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
@@ -1730,10 +1826,10 @@
|
||||
"$ref": "#/components/schemas/Union_"
|
||||
},
|
||||
"agent": {
|
||||
"$ref": "#/components/schemas/Union_2"
|
||||
"$ref": "#/components/schemas/Union_3"
|
||||
},
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Union_3"
|
||||
"$ref": "#/components/schemas/Union_4"
|
||||
},
|
||||
"files": {
|
||||
"$ref": "#/components/schemas/Arrays_4"
|
||||
@@ -1745,10 +1841,10 @@
|
||||
"$ref": "#/components/schemas/Arrays_6"
|
||||
},
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
},
|
||||
"resume": {
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
}
|
||||
},
|
||||
"required": ["command"],
|
||||
@@ -1834,13 +1930,13 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
},
|
||||
"skill": {
|
||||
"type": "string"
|
||||
},
|
||||
"resume": {
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
}
|
||||
},
|
||||
"required": ["skill"],
|
||||
@@ -1947,7 +2043,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
@@ -1959,10 +2055,10 @@
|
||||
"$ref": "#/components/schemas/Objects_"
|
||||
},
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
},
|
||||
"resume": {
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
}
|
||||
},
|
||||
"required": ["text"],
|
||||
@@ -2167,10 +2263,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
},
|
||||
"delivery": {
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
"$ref": "#/components/schemas/Union_9"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
@@ -2373,7 +2469,7 @@
|
||||
]
|
||||
},
|
||||
"files": {
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
}
|
||||
},
|
||||
"required": ["messageID"],
|
||||
@@ -3344,7 +3440,7 @@
|
||||
"name": "follow",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
@@ -3499,7 +3595,7 @@
|
||||
"name": "continue",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
"$ref": "#/components/schemas/Union_8"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
@@ -3547,7 +3643,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Interrupt active execution owned by this OpenCode process. Idle interruption is a no-op. When continue=true, execution resumes pending steering input while queued work remains parked.",
|
||||
"description": "Interrupt active execution owned by this OpenCode process. Idle interruption is a no-op. When continue=true, execution resumes pending steering input and next-in-line control items (manual compaction, moves) while queued prompts remain parked.",
|
||||
"summary": "Interrupt session execution"
|
||||
}
|
||||
},
|
||||
@@ -4155,7 +4251,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Union_3"
|
||||
"$ref": "#/components/schemas/Union_4"
|
||||
}
|
||||
},
|
||||
"required": ["prompt"],
|
||||
@@ -4617,7 +4713,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"answer": {
|
||||
"$ref": "#/components/schemas/Union_12"
|
||||
"$ref": "#/components/schemas/Union_13"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/components/schemas/Union_"
|
||||
@@ -4718,7 +4814,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"answer": {
|
||||
"$ref": "#/components/schemas/Union_12"
|
||||
"$ref": "#/components/schemas/Union_13"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/components/schemas/Union_"
|
||||
@@ -6504,14 +6600,7 @@
|
||||
"name": "projectID",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
"$ref": "#/components/schemas/Union_2"
|
||||
},
|
||||
"required": false
|
||||
}
|
||||
@@ -6737,7 +6826,7 @@
|
||||
"$ref": "#/components/schemas/Permission.Source"
|
||||
},
|
||||
"agent": {
|
||||
"$ref": "#/components/schemas/Union_2"
|
||||
"$ref": "#/components/schemas/Union_3"
|
||||
}
|
||||
},
|
||||
"required": ["action", "resources"],
|
||||
@@ -9914,15 +10003,116 @@
|
||||
"required": ["_tag", "agentID", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Plugin.Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
"Plugin.Source": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["builtin"]
|
||||
}
|
||||
},
|
||||
"required": ["type"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["package"]
|
||||
},
|
||||
"package": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "package"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["local"]
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["type", "path"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["sdk"]
|
||||
}
|
||||
},
|
||||
"required": ["type"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["id"],
|
||||
"additionalProperties": false
|
||||
]
|
||||
},
|
||||
"Plugin.Info": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/components/schemas/Plugin.Source"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["active"]
|
||||
},
|
||||
"tui": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["id", "source", "status", "tui"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"$ref": "#/components/schemas/Plugin.Source"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["failed"]
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"tui": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["source", "status", "error", "tui"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
},
|
||||
"Union_2": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Session.ForkBoundary": {
|
||||
"anyOf": [
|
||||
@@ -10213,7 +10403,239 @@
|
||||
"required": ["_tag", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Union_2": {
|
||||
"SessionStats.Activity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"date": {
|
||||
"type": "string"
|
||||
},
|
||||
"steps": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["date", "steps"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"SessionStats.ModelUsage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Model.Ref"
|
||||
},
|
||||
"steps": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"tokens": {
|
||||
"$ref": "#/components/schemas/TokenUsage.Info"
|
||||
},
|
||||
"cost": {
|
||||
"$ref": "#/components/schemas/Money.USD"
|
||||
}
|
||||
},
|
||||
"required": ["model", "steps", "tokens", "cost"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"SessionStats.ToolUsage": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"calls": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"succeeded": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"failed": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unfinished": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"durationP50": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": ["name", "calls", "succeeded", "failed", "unfinished"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"SessionStats.Info": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"range": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"from": {
|
||||
"type": "number"
|
||||
},
|
||||
"to": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": ["from", "to"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"sessions": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"subagents": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"prompts": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"steps": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"tokens": {
|
||||
"$ref": "#/components/schemas/TokenUsage.Info"
|
||||
},
|
||||
"cost": {
|
||||
"$ref": "#/components/schemas/Money.USD"
|
||||
},
|
||||
"tools": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"calls": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"succeeded": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"failed": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"unfinished": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["calls", "succeeded", "failed", "unfinished"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"activeDays": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"streak": {
|
||||
"type": "integer",
|
||||
"allOf": [
|
||||
{
|
||||
"minimum": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"activity": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SessionStats.Activity"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SessionStats.ModelUsage"
|
||||
}
|
||||
},
|
||||
"toolUsage": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SessionStats.ToolUsage"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"range",
|
||||
"sessions",
|
||||
"subagents",
|
||||
"prompts",
|
||||
"steps",
|
||||
"tokens",
|
||||
"cost",
|
||||
"tools",
|
||||
"activeDays",
|
||||
"streak",
|
||||
"activity",
|
||||
"models",
|
||||
"toolUsage"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Union_3": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
@@ -10223,7 +10645,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Union_3": {
|
||||
"Union_4": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Model.Ref"
|
||||
@@ -10233,7 +10655,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Union_4": {
|
||||
"Union_5": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Location.Ref"
|
||||
@@ -10480,14 +10902,11 @@
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"mention": {
|
||||
"$ref": "#/components/schemas/Prompt.Mention"
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "text"],
|
||||
"required": ["id", "name"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Arrays_3": {
|
||||
@@ -10629,11 +11048,11 @@
|
||||
"required": ["id", "time", "type", "skill", "name", "text"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Union_5": {
|
||||
"Union_6": {
|
||||
"type": "string",
|
||||
"enum": ["running", "exited", "timeout", "killed"]
|
||||
},
|
||||
"Union_6": {
|
||||
"Union_7": {
|
||||
"type": "string",
|
||||
"enum": ["Infinity", "-Infinity", "NaN"]
|
||||
},
|
||||
@@ -10709,7 +11128,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Union_5"
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
},
|
||||
"exit": {
|
||||
"anyOf": [
|
||||
@@ -10717,7 +11136,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -11003,6 +11422,9 @@
|
||||
"required": ["type", "id", "name", "state", "time"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Session.Message.ProviderState_4": {
|
||||
"type": "object"
|
||||
},
|
||||
"Session.Message.Assistant.Retry": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11099,6 +11521,12 @@
|
||||
"type": "string",
|
||||
"enum": ["stop", "length", "tool-calls", "content-filter", "error", "unknown"]
|
||||
},
|
||||
"rawFinish": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerState": {
|
||||
"$ref": "#/components/schemas/Session.Message.ProviderState_4"
|
||||
},
|
||||
"cost": {
|
||||
"$ref": "#/components/schemas/Money.USD"
|
||||
},
|
||||
@@ -11299,7 +11727,7 @@
|
||||
"required": ["_tag", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Union_7": {
|
||||
"Union_8": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
@@ -11426,7 +11854,7 @@
|
||||
"type": "string",
|
||||
"enum": ["steer", "queue"]
|
||||
},
|
||||
"Union_8": {
|
||||
"Union_9": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Session.Inbox.Delivery"
|
||||
@@ -11436,7 +11864,7 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Union_9": {
|
||||
"Union_10": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
@@ -11504,7 +11932,7 @@
|
||||
"Objects_3": {
|
||||
"type": "object"
|
||||
},
|
||||
"Union_10": {
|
||||
"Union_11": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
@@ -12200,13 +12628,13 @@
|
||||
"required": ["_tag", "providerID", "message"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Union_11": {
|
||||
"Union_12": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -12226,7 +12654,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
"$ref": "#/components/schemas/Union_12"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
@@ -12351,7 +12779,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -12361,7 +12789,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -12371,7 +12799,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12407,7 +12835,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -12417,7 +12845,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -12427,7 +12855,7 @@
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
"$ref": "#/components/schemas/Union_7"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12755,7 +13183,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
"$ref": "#/components/schemas/Union_12"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
@@ -12774,7 +13202,7 @@
|
||||
"$ref": "#/components/schemas/Form.Value"
|
||||
}
|
||||
},
|
||||
"Union_12": {
|
||||
"Union_13": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Form.Answer"
|
||||
@@ -12788,10 +13216,10 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created": {
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
"$ref": "#/components/schemas/Union_12"
|
||||
},
|
||||
"expires": {
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
"$ref": "#/components/schemas/Union_12"
|
||||
}
|
||||
},
|
||||
"required": ["created", "expires"],
|
||||
@@ -13844,7 +14272,7 @@
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/Union_5"
|
||||
"$ref": "#/components/schemas/Union_6"
|
||||
},
|
||||
"command": {
|
||||
"type": "string"
|
||||
@@ -14024,7 +14452,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"forceRequired": {
|
||||
"$ref": "#/components/schemas/Union_10"
|
||||
"$ref": "#/components/schemas/Union_11"
|
||||
}
|
||||
},
|
||||
"required": ["message"],
|
||||
@@ -14144,7 +14572,7 @@
|
||||
"required": ["providerID", "results"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Union_13": {
|
||||
"Union_14": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
@@ -14200,7 +14628,7 @@
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Union_13"
|
||||
"$ref": "#/components/schemas/Union_14"
|
||||
},
|
||||
"request": {
|
||||
"type": "object",
|
||||
@@ -14323,7 +14751,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Union_13"
|
||||
"$ref": "#/components/schemas/Union_14"
|
||||
},
|
||||
"subtask": {
|
||||
"type": "boolean"
|
||||
@@ -14581,7 +15009,7 @@
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"$ref": "#/components/schemas/Union_13"
|
||||
"$ref": "#/components/schemas/Union_14"
|
||||
},
|
||||
"default_agent": {
|
||||
"type": "string"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { SessionTransfer } from "@opencode-ai/schema/session-transfer"
|
||||
import { SessionInbox } from "@opencode-ai/schema/session-inbox"
|
||||
import { PromptInput } from "@opencode-ai/schema/prompt-input"
|
||||
import { Session } from "@opencode-ai/schema/session"
|
||||
import { SessionStats } from "@opencode-ai/schema/session-stats"
|
||||
import { InstructionEntry } from "@opencode-ai/schema/instruction-entry"
|
||||
import { Project } from "@opencode-ai/schema/project"
|
||||
import { AbsolutePath, PositiveInt, RelativePath, statics } from "@opencode-ai/schema/schema"
|
||||
@@ -146,6 +147,24 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("session.stats", "/api/session/stats", {
|
||||
query: Schema.Struct({
|
||||
from: Schema.NumberFromString.pipe(Schema.optional),
|
||||
to: Schema.NumberFromString.pipe(Schema.optional),
|
||||
project: Project.ID.pipe(Schema.optional),
|
||||
timezone: Schema.String.pipe(Schema.optional),
|
||||
}),
|
||||
success: Schema.Struct({ data: SessionStats.Info }),
|
||||
error: InvalidRequestError,
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.stats",
|
||||
summary: "Get session statistics",
|
||||
description: "Aggregate local session activity, usage, and tool reliability for a time range.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("session.create", "/api/session", {
|
||||
payload: Schema.Struct({
|
||||
|
||||
@@ -24,6 +24,7 @@ export { Vcs } from "./vcs.js"
|
||||
export { SessionInbox } from "./session-inbox.js"
|
||||
export { SessionError } from "./session-error.js"
|
||||
export { SessionMessage } from "./session-message.js"
|
||||
export { SessionStats } from "./session-stats.js"
|
||||
export { SessionTransfer } from "./session-transfer.js"
|
||||
export { Snapshot } from "./snapshot.js"
|
||||
export { Shell } from "./shell.js"
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
export * as SessionStats from "./session-stats.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { Model } from "./model.js"
|
||||
import { Money } from "./money.js"
|
||||
import { DateTimeUtcFromMillis, NonNegativeInt, optional } from "./schema.js"
|
||||
import { TokenUsage } from "./token-usage.js"
|
||||
|
||||
export const Activity = Schema.Struct({
|
||||
date: Schema.String,
|
||||
steps: NonNegativeInt,
|
||||
}).annotate({ identifier: "SessionStats.Activity" })
|
||||
export type Activity = typeof Activity.Type
|
||||
|
||||
export const ModelUsage = Schema.Struct({
|
||||
model: Model.Ref,
|
||||
steps: NonNegativeInt,
|
||||
tokens: TokenUsage.Info,
|
||||
cost: Money.USD,
|
||||
}).annotate({ identifier: "SessionStats.ModelUsage" })
|
||||
export type ModelUsage = typeof ModelUsage.Type
|
||||
|
||||
export const ToolUsage = Schema.Struct({
|
||||
name: Schema.String,
|
||||
calls: NonNegativeInt,
|
||||
succeeded: NonNegativeInt,
|
||||
failed: NonNegativeInt,
|
||||
unfinished: NonNegativeInt,
|
||||
durationP50: Schema.Finite.pipe(optional),
|
||||
}).annotate({ identifier: "SessionStats.ToolUsage" })
|
||||
export type ToolUsage = typeof ToolUsage.Type
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
range: Schema.Struct({
|
||||
from: DateTimeUtcFromMillis,
|
||||
to: DateTimeUtcFromMillis,
|
||||
}),
|
||||
sessions: NonNegativeInt,
|
||||
subagents: NonNegativeInt,
|
||||
prompts: NonNegativeInt,
|
||||
steps: NonNegativeInt,
|
||||
tokens: TokenUsage.Info,
|
||||
cost: Money.USD,
|
||||
tools: Schema.Struct({
|
||||
calls: NonNegativeInt,
|
||||
succeeded: NonNegativeInt,
|
||||
failed: NonNegativeInt,
|
||||
unfinished: NonNegativeInt,
|
||||
}),
|
||||
activeDays: NonNegativeInt,
|
||||
streak: NonNegativeInt,
|
||||
activity: Schema.Array(Activity),
|
||||
models: Schema.Array(ModelUsage),
|
||||
toolUsage: Schema.Array(ToolUsage),
|
||||
}).annotate({ identifier: "SessionStats.Info" })
|
||||
export type Info = typeof Info.Type
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Session } from "@opencode-ai/core/session"
|
||||
import { SessionStats } from "@opencode-ai/core/session/stats"
|
||||
import { SessionTransfer } from "@opencode-ai/core/session/transfer"
|
||||
import { InstructionEntry } from "@opencode-ai/core/session/instruction-entry"
|
||||
import { DateTime, Effect, Stream } from "effect"
|
||||
@@ -88,6 +89,26 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||
}
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.stats",
|
||||
Effect.fn(function* (ctx) {
|
||||
if (ctx.query.from !== undefined && ctx.query.to !== undefined && ctx.query.from >= ctx.query.to)
|
||||
return yield* new InvalidRequestError({ message: "Stats range must end after it starts" })
|
||||
const timezone = ctx.query.timezone ?? "UTC"
|
||||
yield* Effect.try({
|
||||
try: () => new Intl.DateTimeFormat("en-US", { timeZone: timezone }),
|
||||
catch: () => new InvalidRequestError({ message: `Invalid time zone: ${timezone}` }),
|
||||
})
|
||||
return {
|
||||
data: yield* SessionStats.get({
|
||||
from: ctx.query.from,
|
||||
to: ctx.query.to,
|
||||
projectID: ctx.query.project,
|
||||
timezone,
|
||||
}),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.create",
|
||||
Effect.fn(function* (ctx) {
|
||||
|
||||
@@ -59,11 +59,6 @@
|
||||
"node": "./src/attention-sounds.node.ts",
|
||||
"default": "./src/attention-sounds.bun.ts"
|
||||
},
|
||||
"#terminal-win32": {
|
||||
"bun": "./src/terminal-win32.bun.ts",
|
||||
"node": "./src/terminal-win32.node.ts",
|
||||
"default": "./src/terminal-win32.bun.ts"
|
||||
},
|
||||
"#string-width": {
|
||||
"bun": "./src/util/string-width.bun.ts",
|
||||
"node": "./src/util/string-width.node.ts",
|
||||
|
||||
@@ -96,7 +96,6 @@ import { CommandPaletteDialog } from "./component/command-palette"
|
||||
import { COMMAND_PALETTE_COMMAND, Keymap, type KeymapCommand } from "./context/keymap"
|
||||
|
||||
import { DialogVariant } from "./component/dialog-variant"
|
||||
import { win32DisableProcessedInput, win32FlushInputBuffer } from "./terminal-win32"
|
||||
import { destroyRenderer } from "./util/renderer"
|
||||
import { cliErrorMessage, errorFormat } from "./util/error"
|
||||
import { AttentionProvider } from "./context/attention"
|
||||
@@ -266,7 +265,6 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
Effect.catch((error) => Effect.sync(() => log("error", "Failed to dispose TUI clipboard", { error }))),
|
||||
),
|
||||
)
|
||||
win32DisableProcessedInput()
|
||||
const finalizers = new Set<() => Promise<void>>()
|
||||
yield* Effect.addFinalizer(() =>
|
||||
Effect.promise(async () => {
|
||||
@@ -450,7 +448,6 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
}),
|
||||
)
|
||||
yield* Effect.sync(() => {
|
||||
win32FlushInputBuffer()
|
||||
if (result.reason !== undefined)
|
||||
process.stderr.write((cliErrorMessage(result.reason) ?? errorFormat(result.reason)) + "\n")
|
||||
if (result.epilogue) process.stdout.write(result.epilogue + "\n")
|
||||
|
||||
@@ -189,7 +189,7 @@ export const Definitions = {
|
||||
"prompt.clear": keybind("ctrl+c", "Clear input field"),
|
||||
"prompt.paste": keybind({ key: "ctrl+v", preventDefault: false }, "Paste from clipboard"),
|
||||
"input.submit": keybind("return", "Submit input"),
|
||||
"input.newline": keybind("shift+return,ctrl+return,ctrl+j", "Insert newline in input"),
|
||||
"input.newline": keybind("shift+return,ctrl+return,alt+return,ctrl+j", "Insert newline in input"),
|
||||
"input.move.left": keybind("left,ctrl+b", "Move cursor left in input"),
|
||||
"input.move.right": keybind("right,ctrl+f", "Move cursor right in input"),
|
||||
"input.move.up": keybind("up", "Move cursor up in input"),
|
||||
|
||||
@@ -174,7 +174,7 @@ export const Definitions = {
|
||||
input_clear: keybind("ctrl+c", "Clear input field"),
|
||||
input_paste: keybind({ key: "ctrl+v", preventDefault: false }, "Paste from clipboard"),
|
||||
input_submit: keybind("return", "Submit input"),
|
||||
input_newline: keybind("shift+return,ctrl+return,ctrl+j", "Insert newline in input"),
|
||||
input_newline: keybind("shift+return,ctrl+return,alt+return,ctrl+j", "Insert newline in input"),
|
||||
input_move_left: keybind("left,ctrl+b", "Move cursor left in input"),
|
||||
input_move_right: keybind("right,ctrl+f", "Move cursor right in input"),
|
||||
input_move_up: keybind("up", "Move cursor up in input"),
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { dlopen, ptr } from "bun:ffi"
|
||||
import type { ReadStream } from "node:tty"
|
||||
|
||||
const STD_INPUT_HANDLE = -10
|
||||
const ENABLE_PROCESSED_INPUT = 0x0001
|
||||
|
||||
const kernel = () =>
|
||||
dlopen("kernel32.dll", {
|
||||
GetStdHandle: { args: ["i32"], returns: "ptr" },
|
||||
GetConsoleMode: { args: ["ptr", "ptr"], returns: "i32" },
|
||||
SetConsoleMode: { args: ["ptr", "u32"], returns: "i32" },
|
||||
FlushConsoleInputBuffer: { args: ["ptr"], returns: "i32" },
|
||||
})
|
||||
|
||||
let k32: ReturnType<typeof kernel> | undefined
|
||||
|
||||
function load() {
|
||||
if (process.platform !== "win32") return false
|
||||
try {
|
||||
k32 ??= kernel()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear ENABLE_PROCESSED_INPUT on the console stdin handle.
|
||||
*/
|
||||
export function win32DisableProcessedInput() {
|
||||
if (process.platform !== "win32") return
|
||||
if (!process.stdin.isTTY) return
|
||||
if (!load()) return
|
||||
|
||||
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
||||
const buf = new Uint32Array(1)
|
||||
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
||||
|
||||
const mode = buf[0]!
|
||||
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
||||
k32!.symbols.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Discard any queued console input (mouse events, key presses, etc.).
|
||||
*/
|
||||
export function win32FlushInputBuffer() {
|
||||
if (process.platform !== "win32") return
|
||||
if (!process.stdin.isTTY) return
|
||||
if (!load()) return
|
||||
|
||||
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
||||
k32!.symbols.FlushConsoleInputBuffer(handle)
|
||||
}
|
||||
|
||||
let unhook: (() => void) | undefined
|
||||
|
||||
/**
|
||||
* Keep ENABLE_PROCESSED_INPUT disabled.
|
||||
*
|
||||
* On Windows, Ctrl+C becomes a CTRL_C_EVENT (instead of stdin input) when
|
||||
* ENABLE_PROCESSED_INPUT is set. Various runtimes can re-apply console modes
|
||||
* (sometimes on a later tick), and the flag is console-global, not per-process.
|
||||
*
|
||||
* We combine:
|
||||
* - A `setRawMode(...)` hook to re-clear after known raw-mode toggles.
|
||||
* - A low-frequency poll as a backstop for native/external mode changes.
|
||||
*/
|
||||
export function win32InstallCtrlCGuard() {
|
||||
if (process.platform !== "win32") return
|
||||
if (!process.stdin.isTTY) return
|
||||
if (!load()) return
|
||||
if (unhook) return unhook
|
||||
|
||||
const stdin = process.stdin as ReadStream
|
||||
const original = stdin.setRawMode
|
||||
|
||||
const handle = k32!.symbols.GetStdHandle(STD_INPUT_HANDLE)
|
||||
const buf = new Uint32Array(1)
|
||||
|
||||
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
||||
const initial = buf[0]!
|
||||
|
||||
const enforce = () => {
|
||||
if (k32!.symbols.GetConsoleMode(handle, ptr(buf)) === 0) return
|
||||
const mode = buf[0]!
|
||||
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
||||
k32!.symbols.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
||||
}
|
||||
|
||||
// Some runtimes can re-apply console modes on the next tick; enforce twice.
|
||||
const later = () => {
|
||||
enforce()
|
||||
setImmediate(enforce)
|
||||
}
|
||||
|
||||
let wrapped: ReadStream["setRawMode"] | undefined
|
||||
|
||||
if (typeof original === "function") {
|
||||
wrapped = (mode: boolean) => {
|
||||
const result = original.call(stdin, mode)
|
||||
later()
|
||||
return result
|
||||
}
|
||||
|
||||
stdin.setRawMode = wrapped
|
||||
}
|
||||
|
||||
// Ensure it's cleared immediately too (covers any earlier mode changes).
|
||||
later()
|
||||
|
||||
const interval = setInterval(enforce, 100)
|
||||
interval.unref()
|
||||
|
||||
let done = false
|
||||
unhook = () => {
|
||||
if (done) return
|
||||
done = true
|
||||
|
||||
clearInterval(interval)
|
||||
if (wrapped && stdin.setRawMode === wrapped) {
|
||||
stdin.setRawMode = original
|
||||
}
|
||||
|
||||
k32!.symbols.SetConsoleMode(handle, initial)
|
||||
unhook = undefined
|
||||
}
|
||||
|
||||
return unhook
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { dlopen } from "node:ffi"
|
||||
import type { ReadStream } from "node:tty"
|
||||
|
||||
const STD_INPUT_HANDLE = -10
|
||||
const ENABLE_PROCESSED_INPUT = 0x0001
|
||||
|
||||
const kernel = () =>
|
||||
dlopen("kernel32.dll", {
|
||||
GetStdHandle: { arguments: ["i32"], return: "pointer" },
|
||||
GetConsoleMode: { arguments: ["pointer", "pointer"], return: "i32" },
|
||||
SetConsoleMode: { arguments: ["pointer", "u32"], return: "i32" },
|
||||
FlushConsoleInputBuffer: { arguments: ["pointer"], return: "i32" },
|
||||
}).functions
|
||||
|
||||
let k32: ReturnType<typeof kernel> | undefined
|
||||
|
||||
function load() {
|
||||
if (process.platform !== "win32") return false
|
||||
try {
|
||||
k32 ??= kernel()
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function win32DisableProcessedInput() {
|
||||
if (process.platform !== "win32" || !process.stdin.isTTY || !load()) return
|
||||
const handle = k32!.GetStdHandle(STD_INPUT_HANDLE)
|
||||
const buffer = new Uint32Array(1)
|
||||
if (k32!.GetConsoleMode(handle, buffer) === 0) return
|
||||
const mode = buffer[0]!
|
||||
if ((mode & ENABLE_PROCESSED_INPUT) === 0) return
|
||||
k32!.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
||||
}
|
||||
|
||||
export function win32FlushInputBuffer() {
|
||||
if (process.platform !== "win32" || !process.stdin.isTTY || !load()) return
|
||||
k32!.FlushConsoleInputBuffer(k32!.GetStdHandle(STD_INPUT_HANDLE))
|
||||
}
|
||||
|
||||
let unhook: (() => void) | undefined
|
||||
|
||||
export function win32InstallCtrlCGuard() {
|
||||
if (process.platform !== "win32" || !process.stdin.isTTY || !load() || unhook) return unhook
|
||||
const stdin = process.stdin as ReadStream
|
||||
const original = stdin.setRawMode
|
||||
const handle = k32!.GetStdHandle(STD_INPUT_HANDLE)
|
||||
const buffer = new Uint32Array(1)
|
||||
if (k32!.GetConsoleMode(handle, buffer) === 0) return
|
||||
const initial = buffer[0]!
|
||||
const enforce = () => {
|
||||
if (k32!.GetConsoleMode(handle, buffer) === 0) return
|
||||
const mode = buffer[0]!
|
||||
if ((mode & ENABLE_PROCESSED_INPUT) !== 0) k32!.SetConsoleMode(handle, mode & ~ENABLE_PROCESSED_INPUT)
|
||||
}
|
||||
const later = () => {
|
||||
enforce()
|
||||
setImmediate(enforce)
|
||||
}
|
||||
const wrapped: ReadStream["setRawMode"] = (mode) => {
|
||||
const result = original.call(stdin, mode)
|
||||
later()
|
||||
return result
|
||||
}
|
||||
stdin.setRawMode = wrapped
|
||||
later()
|
||||
const interval = setInterval(enforce, 100)
|
||||
interval.unref()
|
||||
unhook = () => {
|
||||
clearInterval(interval)
|
||||
if (stdin.setRawMode === wrapped) stdin.setRawMode = original
|
||||
k32!.SetConsoleMode(handle, initial)
|
||||
unhook = undefined
|
||||
}
|
||||
return unhook
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "#terminal-win32"
|
||||
@@ -107,6 +107,29 @@ test("dialog prompt submit wins when return is also input newline", async () =>
|
||||
}
|
||||
})
|
||||
|
||||
test("alt return inserts a newline with default keybinds", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const confirmed: string[] = []
|
||||
const prompt = await mountPrompt({
|
||||
root: tmp.path,
|
||||
keybinds: {},
|
||||
onConfirm: (value) => confirmed.push(value),
|
||||
})
|
||||
|
||||
try {
|
||||
await wait(() => prompt.app.renderer.currentFocusedEditor instanceof TextareaRenderable)
|
||||
const textarea = prompt.app.renderer.currentFocusedEditor
|
||||
if (!(textarea instanceof TextareaRenderable)) throw new Error("expected focused dialog textarea")
|
||||
|
||||
prompt.app.mockInput.pressEnter({ meta: true })
|
||||
|
||||
expect(confirmed).toEqual([])
|
||||
expect(textarea.plainText).toBe("draft\n")
|
||||
} finally {
|
||||
await prompt.cleanup()
|
||||
}
|
||||
})
|
||||
|
||||
test("dialog prompt submit can be rebound separately from input submit", async () => {
|
||||
await using tmp = await tmpdir()
|
||||
const confirmed: string[] = []
|
||||
|
||||
@@ -21,7 +21,7 @@ describe("run runtime boot", () => {
|
||||
expect(result.keybinds.get("prompt.history.next")?.[0]?.key).toBe("down")
|
||||
expect(result.keybinds.get("prompt.clear")?.[0]?.key).toBe("ctrl+c")
|
||||
expect(result.keybinds.get("input.submit")?.[0]?.key).toBe("return")
|
||||
expect(result.keybinds.get("input.newline")?.[0]?.key).toBe("shift+return,ctrl+return,ctrl+j")
|
||||
expect(result.keybinds.get("input.newline")?.[0]?.key).toBe("shift+return,ctrl+return,alt+return,ctrl+j")
|
||||
expect(result.keybinds.get("prompt.queue")?.[0]?.key).toBe("<leader>return")
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user