@link-assistant/agent 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EXAMPLES.md +383 -0
- package/LICENSE +24 -0
- package/MODELS.md +95 -0
- package/README.md +388 -0
- package/TOOLS.md +134 -0
- package/package.json +89 -0
- package/src/agent/agent.ts +150 -0
- package/src/agent/generate.txt +75 -0
- package/src/auth/index.ts +64 -0
- package/src/bun/index.ts +96 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +119 -0
- package/src/cli/bootstrap.js +41 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/agent.ts +165 -0
- package/src/cli/cmd/cmd.ts +5 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/mcp.ts +80 -0
- package/src/cli/cmd/models.ts +58 -0
- package/src/cli/cmd/run.ts +359 -0
- package/src/cli/cmd/stats.ts +276 -0
- package/src/cli/error.ts +27 -0
- package/src/command/index.ts +73 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/config/config.ts +705 -0
- package/src/config/markdown.ts +41 -0
- package/src/file/ripgrep.ts +391 -0
- package/src/file/time.ts +38 -0
- package/src/file/watcher.ts +75 -0
- package/src/file.ts +6 -0
- package/src/flag/flag.ts +19 -0
- package/src/format/formatter.ts +248 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +72 -0
- package/src/index.js +371 -0
- package/src/mcp/index.ts +289 -0
- package/src/patch/index.ts +622 -0
- package/src/project/bootstrap.ts +22 -0
- package/src/project/instance.ts +67 -0
- package/src/project/project.ts +105 -0
- package/src/project/state.ts +65 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +98 -0
- package/src/provider/opencode.js +47 -0
- package/src/provider/provider.ts +636 -0
- package/src/provider/transform.ts +241 -0
- package/src/server/project.ts +48 -0
- package/src/server/server.ts +249 -0
- package/src/session/agent.js +204 -0
- package/src/session/compaction.ts +249 -0
- package/src/session/index.ts +380 -0
- package/src/session/message-v2.ts +758 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +356 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex.txt +318 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/grok-code.txt +1 -0
- package/src/session/prompt/plan.txt +8 -0
- package/src/session/prompt/polaris.txt +107 -0
- package/src/session/prompt/qwen.txt +109 -0
- package/src/session/prompt/summarize-turn.txt +5 -0
- package/src/session/prompt/summarize.txt +10 -0
- package/src/session/prompt/title.txt +25 -0
- package/src/session/prompt.ts +1390 -0
- package/src/session/retry.ts +53 -0
- package/src/session/revert.ts +108 -0
- package/src/session/status.ts +75 -0
- package/src/session/summary.ts +179 -0
- package/src/session/system.ts +138 -0
- package/src/session/todo.ts +36 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/tool/bash.ts +193 -0
- package/src/tool/bash.txt +121 -0
- package/src/tool/batch.ts +173 -0
- package/src/tool/batch.txt +28 -0
- package/src/tool/codesearch.ts +123 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +604 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +116 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +188 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +201 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +87 -0
- package/src/tool/task.ts +126 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +66 -0
- package/src/tool/webfetch.ts +171 -0
- package/src/tool/webfetch.txt +14 -0
- package/src/tool/websearch.ts +133 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +33 -0
- package/src/tool/write.txt +8 -0
- package/src/util/binary.ts +41 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/error.ts +54 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +69 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +79 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/locale.ts +39 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +177 -0
- package/src/util/queue.ts +19 -0
- package/src/util/rpc.ts +42 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +54 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai"
|
|
2
|
+
import { unique } from "remeda"
|
|
3
|
+
import type { JSONSchema } from "zod/v4/core"
|
|
4
|
+
|
|
5
|
+
export namespace ProviderTransform {
|
|
6
|
+
function normalizeMessages(msgs: ModelMessage[], providerID: string, modelID: string): ModelMessage[] {
|
|
7
|
+
if (modelID.includes("claude")) {
|
|
8
|
+
return msgs.map((msg) => {
|
|
9
|
+
if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
|
|
10
|
+
msg.content = msg.content.map((part) => {
|
|
11
|
+
if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
|
|
12
|
+
return {
|
|
13
|
+
...part,
|
|
14
|
+
toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return part
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
return msg
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
if (providerID === "mistral" || modelID.toLowerCase().includes("mistral")) {
|
|
24
|
+
const result: ModelMessage[] = []
|
|
25
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
26
|
+
const msg = msgs[i]
|
|
27
|
+
const prevMsg = msgs[i - 1]
|
|
28
|
+
const nextMsg = msgs[i + 1]
|
|
29
|
+
|
|
30
|
+
if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
|
|
31
|
+
msg.content = msg.content.map((part) => {
|
|
32
|
+
if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
|
|
33
|
+
// Mistral requires alphanumeric tool call IDs with exactly 9 characters
|
|
34
|
+
const normalizedId = part.toolCallId
|
|
35
|
+
.replace(/[^a-zA-Z0-9]/g, "") // Remove non-alphanumeric characters
|
|
36
|
+
.substring(0, 9) // Take first 9 characters
|
|
37
|
+
.padEnd(9, "0") // Pad with zeros if less than 9 characters
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
...part,
|
|
41
|
+
toolCallId: normalizedId,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return part
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
result.push(msg)
|
|
49
|
+
|
|
50
|
+
// Fix message sequence: tool messages cannot be followed by user messages
|
|
51
|
+
if (msg.role === "tool" && nextMsg?.role === "user") {
|
|
52
|
+
result.push({
|
|
53
|
+
role: "assistant",
|
|
54
|
+
content: [
|
|
55
|
+
{
|
|
56
|
+
type: "text",
|
|
57
|
+
text: "Done.",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return msgs
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function applyCaching(msgs: ModelMessage[], providerID: string): ModelMessage[] {
|
|
70
|
+
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
|
71
|
+
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
|
72
|
+
|
|
73
|
+
const providerOptions = {
|
|
74
|
+
anthropic: {
|
|
75
|
+
cacheControl: { type: "ephemeral" },
|
|
76
|
+
},
|
|
77
|
+
openrouter: {
|
|
78
|
+
cache_control: { type: "ephemeral" },
|
|
79
|
+
},
|
|
80
|
+
bedrock: {
|
|
81
|
+
cachePoint: { type: "ephemeral" },
|
|
82
|
+
},
|
|
83
|
+
openaiCompatible: {
|
|
84
|
+
cache_control: { type: "ephemeral" },
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const msg of unique([...system, ...final])) {
|
|
89
|
+
const shouldUseContentOptions = providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
|
|
90
|
+
|
|
91
|
+
if (shouldUseContentOptions) {
|
|
92
|
+
const lastContent = msg.content[msg.content.length - 1]
|
|
93
|
+
if (lastContent && typeof lastContent === "object") {
|
|
94
|
+
lastContent.providerOptions = {
|
|
95
|
+
...lastContent.providerOptions,
|
|
96
|
+
...providerOptions,
|
|
97
|
+
}
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
msg.providerOptions = {
|
|
103
|
+
...msg.providerOptions,
|
|
104
|
+
...providerOptions,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return msgs
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
|
112
|
+
msgs = normalizeMessages(msgs, providerID, modelID)
|
|
113
|
+
if (providerID === "anthropic" || modelID.includes("anthropic") || modelID.includes("claude")) {
|
|
114
|
+
msgs = applyCaching(msgs, providerID)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return msgs
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function temperature(_providerID: string, modelID: string) {
|
|
121
|
+
if (modelID.toLowerCase().includes("qwen")) return 0.55
|
|
122
|
+
if (modelID.toLowerCase().includes("claude")) return undefined
|
|
123
|
+
if (modelID.toLowerCase().includes("gemini-3-pro")) return 1.0
|
|
124
|
+
return 0
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function topP(_providerID: string, modelID: string) {
|
|
128
|
+
if (modelID.toLowerCase().includes("qwen")) return 1
|
|
129
|
+
return undefined
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function options(
|
|
133
|
+
providerID: string,
|
|
134
|
+
modelID: string,
|
|
135
|
+
npm: string,
|
|
136
|
+
sessionID: string,
|
|
137
|
+
): Record<string, any> | undefined {
|
|
138
|
+
const result: Record<string, any> = {}
|
|
139
|
+
|
|
140
|
+
if (providerID === "openai") {
|
|
141
|
+
result["promptCacheKey"] = sessionID
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (modelID.includes("gpt-5") && !modelID.includes("gpt-5-chat")) {
|
|
145
|
+
if (modelID.includes("codex")) {
|
|
146
|
+
result["store"] = false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!modelID.includes("codex") && !modelID.includes("gpt-5-pro")) {
|
|
150
|
+
result["reasoningEffort"] = "medium"
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (modelID.endsWith("gpt-5.1") && providerID !== "azure") {
|
|
154
|
+
result["textVerbosity"] = "low"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (providerID === "opencode") {
|
|
158
|
+
result["promptCacheKey"] = sessionID
|
|
159
|
+
result["include"] = ["reasoning.encrypted_content"]
|
|
160
|
+
result["reasoningSummary"] = "auto"
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function providerOptions(npm: string | undefined, providerID: string, options: { [x: string]: any }) {
|
|
167
|
+
switch (npm) {
|
|
168
|
+
case "@ai-sdk/openai":
|
|
169
|
+
case "@ai-sdk/azure":
|
|
170
|
+
return {
|
|
171
|
+
["openai" as string]: options,
|
|
172
|
+
}
|
|
173
|
+
case "@ai-sdk/amazon-bedrock":
|
|
174
|
+
return {
|
|
175
|
+
["bedrock" as string]: options,
|
|
176
|
+
}
|
|
177
|
+
case "@ai-sdk/anthropic":
|
|
178
|
+
return {
|
|
179
|
+
["anthropic" as string]: options,
|
|
180
|
+
}
|
|
181
|
+
case "@ai-sdk/gateway":
|
|
182
|
+
return {
|
|
183
|
+
["gateway" as string]: options,
|
|
184
|
+
}
|
|
185
|
+
default:
|
|
186
|
+
return {
|
|
187
|
+
[providerID]: options,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function maxOutputTokens(
|
|
193
|
+
npm: string,
|
|
194
|
+
options: Record<string, any>,
|
|
195
|
+
modelLimit: number,
|
|
196
|
+
globalLimit: number,
|
|
197
|
+
): number {
|
|
198
|
+
const modelCap = modelLimit || globalLimit
|
|
199
|
+
const standardLimit = Math.min(modelCap, globalLimit)
|
|
200
|
+
|
|
201
|
+
if (npm === "@ai-sdk/anthropic") {
|
|
202
|
+
const thinking = options?.["thinking"]
|
|
203
|
+
const budgetTokens = typeof thinking?.["budgetTokens"] === "number" ? thinking["budgetTokens"] : 0
|
|
204
|
+
const enabled = thinking?.["type"] === "enabled"
|
|
205
|
+
if (enabled && budgetTokens > 0) {
|
|
206
|
+
// Return text tokens so that text + thinking <= model cap, preferring 32k text when possible.
|
|
207
|
+
if (budgetTokens + standardLimit <= modelCap) {
|
|
208
|
+
return standardLimit
|
|
209
|
+
}
|
|
210
|
+
return modelCap - budgetTokens
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return standardLimit
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function schema(_providerID: string, _modelID: string, schema: JSONSchema.BaseSchema) {
|
|
218
|
+
/*
|
|
219
|
+
if (["openai", "azure"].includes(providerID)) {
|
|
220
|
+
if (schema.type === "object" && schema.properties) {
|
|
221
|
+
for (const [key, value] of Object.entries(schema.properties)) {
|
|
222
|
+
if (schema.required?.includes(key)) continue
|
|
223
|
+
schema.properties[key] = {
|
|
224
|
+
anyOf: [
|
|
225
|
+
value as JSONSchema.JSONSchema,
|
|
226
|
+
{
|
|
227
|
+
type: "null",
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (providerID === "google") {
|
|
236
|
+
}
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
return schema
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Hono } from "hono"
|
|
2
|
+
import { describeRoute } from "hono-openapi"
|
|
3
|
+
import { resolver } from "hono-openapi"
|
|
4
|
+
import { Instance } from "../project/instance"
|
|
5
|
+
import { Project } from "../project/project"
|
|
6
|
+
|
|
7
|
+
export const ProjectRoute = new Hono()
|
|
8
|
+
.get(
|
|
9
|
+
"/",
|
|
10
|
+
describeRoute({
|
|
11
|
+
description: "List all projects",
|
|
12
|
+
operationId: "project.list",
|
|
13
|
+
responses: {
|
|
14
|
+
200: {
|
|
15
|
+
description: "List of projects",
|
|
16
|
+
content: {
|
|
17
|
+
"application/json": {
|
|
18
|
+
schema: resolver(Project.Info.array()),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
async (c) => {
|
|
25
|
+
const projects = await Project.list()
|
|
26
|
+
return c.json(projects)
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
.get(
|
|
30
|
+
"/current",
|
|
31
|
+
describeRoute({
|
|
32
|
+
description: "Get the current project",
|
|
33
|
+
operationId: "project.current",
|
|
34
|
+
responses: {
|
|
35
|
+
200: {
|
|
36
|
+
description: "Current project",
|
|
37
|
+
content: {
|
|
38
|
+
"application/json": {
|
|
39
|
+
schema: resolver(Project.Info),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
async (c) => {
|
|
46
|
+
return c.json(Instance.project)
|
|
47
|
+
},
|
|
48
|
+
)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { Log } from "../util/log"
|
|
2
|
+
import { describeRoute, validator, resolver } from "hono-openapi"
|
|
3
|
+
import { Hono } from "hono"
|
|
4
|
+
import { cors } from "hono/cors"
|
|
5
|
+
import { stream } from "hono/streaming"
|
|
6
|
+
import { Session } from "../session"
|
|
7
|
+
import z from "zod"
|
|
8
|
+
import { NamedError } from "../util/error"
|
|
9
|
+
import { MessageV2 } from "../session/message-v2"
|
|
10
|
+
import { Instance } from "../project/instance"
|
|
11
|
+
import { SessionPrompt } from "../session/prompt"
|
|
12
|
+
import { Storage } from "../storage/storage"
|
|
13
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status"
|
|
14
|
+
import { lazy } from "../util/lazy"
|
|
15
|
+
|
|
16
|
+
const ERRORS = {
|
|
17
|
+
400: {
|
|
18
|
+
description: "Bad request",
|
|
19
|
+
content: {
|
|
20
|
+
"application/json": {
|
|
21
|
+
schema: resolver(
|
|
22
|
+
z
|
|
23
|
+
.object({
|
|
24
|
+
data: z.any(),
|
|
25
|
+
errors: z.array(z.record(z.string(), z.any())),
|
|
26
|
+
success: z.literal(false),
|
|
27
|
+
})
|
|
28
|
+
.meta({
|
|
29
|
+
ref: "BadRequestError",
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
404: {
|
|
36
|
+
description: "Not found",
|
|
37
|
+
content: {
|
|
38
|
+
"application/json": {
|
|
39
|
+
schema: resolver(Storage.NotFoundError.Schema),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
} as const
|
|
44
|
+
|
|
45
|
+
function errors(...codes: number[]) {
|
|
46
|
+
return Object.fromEntries(codes.map((code) => [code, ERRORS[code as keyof typeof ERRORS]]))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export namespace Server {
|
|
50
|
+
const log = Log.create({ service: "server" })
|
|
51
|
+
|
|
52
|
+
const app = new Hono()
|
|
53
|
+
export const App = lazy(() =>
|
|
54
|
+
app
|
|
55
|
+
.onError((err, c) => {
|
|
56
|
+
log.error("failed", {
|
|
57
|
+
error: err,
|
|
58
|
+
})
|
|
59
|
+
if (err instanceof NamedError) {
|
|
60
|
+
let status: ContentfulStatusCode
|
|
61
|
+
if (err instanceof Storage.NotFoundError) status = 404
|
|
62
|
+
else status = 500
|
|
63
|
+
return c.json(err.toObject(), { status })
|
|
64
|
+
}
|
|
65
|
+
const message = err instanceof Error && err.stack ? err.stack : err.toString()
|
|
66
|
+
return c.json(new NamedError.Unknown({ message }).toObject(), {
|
|
67
|
+
status: 500,
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
.use(async (c, next) => {
|
|
71
|
+
log.info("request", {
|
|
72
|
+
method: c.req.method,
|
|
73
|
+
path: c.req.path,
|
|
74
|
+
})
|
|
75
|
+
const timer = log.time("request", {
|
|
76
|
+
method: c.req.method,
|
|
77
|
+
path: c.req.path,
|
|
78
|
+
})
|
|
79
|
+
await next()
|
|
80
|
+
timer.stop()
|
|
81
|
+
})
|
|
82
|
+
.use(cors())
|
|
83
|
+
.get("/health", (c) => {
|
|
84
|
+
return c.json({ status: "ok" })
|
|
85
|
+
})
|
|
86
|
+
.post(
|
|
87
|
+
"/session",
|
|
88
|
+
describeRoute({
|
|
89
|
+
description: "Create a new session",
|
|
90
|
+
operationId: "session.create",
|
|
91
|
+
responses: {
|
|
92
|
+
200: {
|
|
93
|
+
description: "Created session",
|
|
94
|
+
content: {
|
|
95
|
+
"application/json": {
|
|
96
|
+
schema: resolver(Session.Info),
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
validator("json", z.object({ title: z.string().optional() }).optional()),
|
|
103
|
+
async (c) => {
|
|
104
|
+
const body = c.req.valid("json") || {}
|
|
105
|
+
const session = await Session.create({
|
|
106
|
+
title: body.title,
|
|
107
|
+
})
|
|
108
|
+
return c.json(session)
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
.get(
|
|
112
|
+
"/session",
|
|
113
|
+
describeRoute({
|
|
114
|
+
description: "List all sessions",
|
|
115
|
+
operationId: "session.list",
|
|
116
|
+
responses: {
|
|
117
|
+
200: {
|
|
118
|
+
description: "List of sessions",
|
|
119
|
+
content: {
|
|
120
|
+
"application/json": {
|
|
121
|
+
schema: resolver(Session.Info.array()),
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
}),
|
|
127
|
+
async (c) => {
|
|
128
|
+
const sessions = await Session.list()
|
|
129
|
+
return c.json(sessions)
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
.get(
|
|
133
|
+
"/session/:id",
|
|
134
|
+
describeRoute({
|
|
135
|
+
description: "Get a session",
|
|
136
|
+
operationId: "session.get",
|
|
137
|
+
responses: {
|
|
138
|
+
200: {
|
|
139
|
+
description: "Session info",
|
|
140
|
+
content: {
|
|
141
|
+
"application/json": {
|
|
142
|
+
schema: resolver(Session.Info),
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
...errors(404),
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
validator(
|
|
150
|
+
"param",
|
|
151
|
+
z.object({
|
|
152
|
+
id: z.string(),
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
async (c) => {
|
|
156
|
+
const session = await Session.get(c.req.valid("param").id)
|
|
157
|
+
return c.json(session)
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
.post(
|
|
161
|
+
"/session/:id/message",
|
|
162
|
+
describeRoute({
|
|
163
|
+
description: "Create and send a new message to a session",
|
|
164
|
+
operationId: "session.prompt",
|
|
165
|
+
responses: {
|
|
166
|
+
200: {
|
|
167
|
+
description: "Created message",
|
|
168
|
+
content: {
|
|
169
|
+
"application/json": {
|
|
170
|
+
schema: resolver(
|
|
171
|
+
z.object({
|
|
172
|
+
info: MessageV2.Assistant,
|
|
173
|
+
parts: MessageV2.Part.array(),
|
|
174
|
+
}),
|
|
175
|
+
),
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
...errors(400, 404),
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
validator(
|
|
183
|
+
"param",
|
|
184
|
+
z.object({
|
|
185
|
+
id: z.string().meta({ description: "Session ID" }),
|
|
186
|
+
}),
|
|
187
|
+
),
|
|
188
|
+
validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
|
|
189
|
+
async (c) => {
|
|
190
|
+
c.status(200)
|
|
191
|
+
c.header("Content-Type", "application/json")
|
|
192
|
+
return stream(c, async (stream) => {
|
|
193
|
+
const sessionID = c.req.valid("param").id
|
|
194
|
+
const body = c.req.valid("json")
|
|
195
|
+
const msg = await SessionPrompt.prompt({ ...body, sessionID })
|
|
196
|
+
stream.write(JSON.stringify(msg))
|
|
197
|
+
})
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
.get(
|
|
201
|
+
"/session/:id/message",
|
|
202
|
+
describeRoute({
|
|
203
|
+
description: "List messages for a session",
|
|
204
|
+
operationId: "session.messages",
|
|
205
|
+
responses: {
|
|
206
|
+
200: {
|
|
207
|
+
description: "List of messages",
|
|
208
|
+
content: {
|
|
209
|
+
"application/json": {
|
|
210
|
+
schema: resolver(MessageV2.WithParts.array()),
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
...errors(400, 404),
|
|
215
|
+
},
|
|
216
|
+
}),
|
|
217
|
+
validator(
|
|
218
|
+
"param",
|
|
219
|
+
z.object({
|
|
220
|
+
id: z.string().meta({ description: "Session ID" }),
|
|
221
|
+
}),
|
|
222
|
+
),
|
|
223
|
+
validator(
|
|
224
|
+
"query",
|
|
225
|
+
z.object({
|
|
226
|
+
limit: z.coerce.number().optional(),
|
|
227
|
+
}),
|
|
228
|
+
),
|
|
229
|
+
async (c) => {
|
|
230
|
+
const query = c.req.valid("query")
|
|
231
|
+
const messages = await Session.messages({
|
|
232
|
+
sessionID: c.req.valid("param").id,
|
|
233
|
+
limit: query.limit,
|
|
234
|
+
})
|
|
235
|
+
return c.json(messages)
|
|
236
|
+
},
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
export function listen(opts: { port: number; hostname: string }) {
|
|
241
|
+
const server = Bun.serve({
|
|
242
|
+
port: opts.port,
|
|
243
|
+
hostname: opts.hostname,
|
|
244
|
+
idleTimeout: 0,
|
|
245
|
+
fetch: App().fetch,
|
|
246
|
+
})
|
|
247
|
+
return server
|
|
248
|
+
}
|
|
249
|
+
}
|