@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.
Files changed (133) hide show
  1. package/EXAMPLES.md +383 -0
  2. package/LICENSE +24 -0
  3. package/MODELS.md +95 -0
  4. package/README.md +388 -0
  5. package/TOOLS.md +134 -0
  6. package/package.json +89 -0
  7. package/src/agent/agent.ts +150 -0
  8. package/src/agent/generate.txt +75 -0
  9. package/src/auth/index.ts +64 -0
  10. package/src/bun/index.ts +96 -0
  11. package/src/bus/global.ts +10 -0
  12. package/src/bus/index.ts +119 -0
  13. package/src/cli/bootstrap.js +41 -0
  14. package/src/cli/bootstrap.ts +17 -0
  15. package/src/cli/cmd/agent.ts +165 -0
  16. package/src/cli/cmd/cmd.ts +5 -0
  17. package/src/cli/cmd/export.ts +88 -0
  18. package/src/cli/cmd/mcp.ts +80 -0
  19. package/src/cli/cmd/models.ts +58 -0
  20. package/src/cli/cmd/run.ts +359 -0
  21. package/src/cli/cmd/stats.ts +276 -0
  22. package/src/cli/error.ts +27 -0
  23. package/src/command/index.ts +73 -0
  24. package/src/command/template/initialize.txt +10 -0
  25. package/src/config/config.ts +705 -0
  26. package/src/config/markdown.ts +41 -0
  27. package/src/file/ripgrep.ts +391 -0
  28. package/src/file/time.ts +38 -0
  29. package/src/file/watcher.ts +75 -0
  30. package/src/file.ts +6 -0
  31. package/src/flag/flag.ts +19 -0
  32. package/src/format/formatter.ts +248 -0
  33. package/src/format/index.ts +137 -0
  34. package/src/global/index.ts +52 -0
  35. package/src/id/id.ts +72 -0
  36. package/src/index.js +371 -0
  37. package/src/mcp/index.ts +289 -0
  38. package/src/patch/index.ts +622 -0
  39. package/src/project/bootstrap.ts +22 -0
  40. package/src/project/instance.ts +67 -0
  41. package/src/project/project.ts +105 -0
  42. package/src/project/state.ts +65 -0
  43. package/src/provider/models-macro.ts +11 -0
  44. package/src/provider/models.ts +98 -0
  45. package/src/provider/opencode.js +47 -0
  46. package/src/provider/provider.ts +636 -0
  47. package/src/provider/transform.ts +241 -0
  48. package/src/server/project.ts +48 -0
  49. package/src/server/server.ts +249 -0
  50. package/src/session/agent.js +204 -0
  51. package/src/session/compaction.ts +249 -0
  52. package/src/session/index.ts +380 -0
  53. package/src/session/message-v2.ts +758 -0
  54. package/src/session/message.ts +189 -0
  55. package/src/session/processor.ts +356 -0
  56. package/src/session/prompt/anthropic-20250930.txt +166 -0
  57. package/src/session/prompt/anthropic.txt +105 -0
  58. package/src/session/prompt/anthropic_spoof.txt +1 -0
  59. package/src/session/prompt/beast.txt +147 -0
  60. package/src/session/prompt/build-switch.txt +5 -0
  61. package/src/session/prompt/codex.txt +318 -0
  62. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  63. package/src/session/prompt/gemini.txt +155 -0
  64. package/src/session/prompt/grok-code.txt +1 -0
  65. package/src/session/prompt/plan.txt +8 -0
  66. package/src/session/prompt/polaris.txt +107 -0
  67. package/src/session/prompt/qwen.txt +109 -0
  68. package/src/session/prompt/summarize-turn.txt +5 -0
  69. package/src/session/prompt/summarize.txt +10 -0
  70. package/src/session/prompt/title.txt +25 -0
  71. package/src/session/prompt.ts +1390 -0
  72. package/src/session/retry.ts +53 -0
  73. package/src/session/revert.ts +108 -0
  74. package/src/session/status.ts +75 -0
  75. package/src/session/summary.ts +179 -0
  76. package/src/session/system.ts +138 -0
  77. package/src/session/todo.ts +36 -0
  78. package/src/snapshot/index.ts +197 -0
  79. package/src/storage/storage.ts +226 -0
  80. package/src/tool/bash.ts +193 -0
  81. package/src/tool/bash.txt +121 -0
  82. package/src/tool/batch.ts +173 -0
  83. package/src/tool/batch.txt +28 -0
  84. package/src/tool/codesearch.ts +123 -0
  85. package/src/tool/codesearch.txt +12 -0
  86. package/src/tool/edit.ts +604 -0
  87. package/src/tool/edit.txt +10 -0
  88. package/src/tool/glob.ts +65 -0
  89. package/src/tool/glob.txt +6 -0
  90. package/src/tool/grep.ts +116 -0
  91. package/src/tool/grep.txt +8 -0
  92. package/src/tool/invalid.ts +17 -0
  93. package/src/tool/ls.ts +110 -0
  94. package/src/tool/ls.txt +1 -0
  95. package/src/tool/multiedit.ts +46 -0
  96. package/src/tool/multiedit.txt +41 -0
  97. package/src/tool/patch.ts +188 -0
  98. package/src/tool/patch.txt +1 -0
  99. package/src/tool/read.ts +201 -0
  100. package/src/tool/read.txt +12 -0
  101. package/src/tool/registry.ts +87 -0
  102. package/src/tool/task.ts +126 -0
  103. package/src/tool/task.txt +60 -0
  104. package/src/tool/todo.ts +39 -0
  105. package/src/tool/todoread.txt +14 -0
  106. package/src/tool/todowrite.txt +167 -0
  107. package/src/tool/tool.ts +66 -0
  108. package/src/tool/webfetch.ts +171 -0
  109. package/src/tool/webfetch.txt +14 -0
  110. package/src/tool/websearch.ts +133 -0
  111. package/src/tool/websearch.txt +11 -0
  112. package/src/tool/write.ts +33 -0
  113. package/src/tool/write.txt +8 -0
  114. package/src/util/binary.ts +41 -0
  115. package/src/util/context.ts +25 -0
  116. package/src/util/defer.ts +12 -0
  117. package/src/util/error.ts +54 -0
  118. package/src/util/eventloop.ts +20 -0
  119. package/src/util/filesystem.ts +69 -0
  120. package/src/util/fn.ts +11 -0
  121. package/src/util/iife.ts +3 -0
  122. package/src/util/keybind.ts +79 -0
  123. package/src/util/lazy.ts +11 -0
  124. package/src/util/locale.ts +39 -0
  125. package/src/util/lock.ts +98 -0
  126. package/src/util/log.ts +177 -0
  127. package/src/util/queue.ts +19 -0
  128. package/src/util/rpc.ts +42 -0
  129. package/src/util/scrap.ts +10 -0
  130. package/src/util/signal.ts +12 -0
  131. package/src/util/timeout.ts +14 -0
  132. package/src/util/token.ts +7 -0
  133. package/src/util/wildcard.ts +54 -0
@@ -0,0 +1,204 @@
1
+ // Permalink: https://github.com/sst/opencode/blob/main/packages/opencode/src/session/prompt.ts
2
+ // Permalink: https://github.com/sst/opencode/blob/main/packages/opencode/src/session/index.ts
3
+ // Permalink: https://github.com/sst/opencode/blob/main/packages/opencode/src/provider/provider.ts
4
+
5
+ import { ToolRegistry } from '../tool/registry.ts'
6
+
7
+ export class Agent {
8
+ constructor() {
9
+ // Generate IDs in the same format as opencode
10
+ const randomId = Math.random().toString(36).substring(2, 15)
11
+ this.sessionID = `ses_${Date.now().toString(36)}${randomId}`
12
+ this.messageID = `msg_${Date.now().toString(36)}${randomId}`
13
+ this.partCounter = 0
14
+ }
15
+
16
+ generatePartId() {
17
+ return `prt_${Date.now().toString(36)}${Math.random().toString(36).substring(2, 15)}`
18
+ }
19
+
20
+ async process(request) {
21
+ const message = request.message || "hi"
22
+ const sessionID = this.sessionID
23
+
24
+ // Generate snapshot hash (mock)
25
+ const snapshot = Math.random().toString(16).substring(2, 42)
26
+
27
+ // Emit step_start like opencode
28
+ this.emitEvent('step_start', {
29
+ part: {
30
+ id: this.generatePartId(),
31
+ sessionID,
32
+ messageID: this.messageID,
33
+ type: 'step-start',
34
+ snapshot
35
+ }
36
+ })
37
+
38
+ // Check if this is a tool request
39
+ if (request.tools && request.tools.length > 0) {
40
+ // Handle tool execution
41
+ const toolsList = await ToolRegistry.tools('', '')
42
+ const toolsMap = Object.fromEntries(toolsList.map(t => [t.id, t]))
43
+ for (const tool of request.tools) {
44
+ const toolFn = toolsMap[tool.name]
45
+ if (toolFn) {
46
+ try {
47
+ const startTime = Date.now()
48
+ const callID = `call_${Math.floor(Math.random() * 100000000)}`
49
+
50
+ // Create OpenCode-compatible context
51
+ const ctx = {
52
+ sessionID,
53
+ messageID: this.messageID,
54
+ agent: 'default',
55
+ callID,
56
+ abort: new AbortController().signal,
57
+ metadata: (data) => {
58
+ // Handle metadata updates during execution
59
+ }
60
+ }
61
+
62
+ const result = await toolFn.execute(tool.params, ctx)
63
+ const endTime = Date.now()
64
+
65
+ // Emit tool_use event
66
+ this.emitEvent('tool_use', {
67
+ part: {
68
+ id: this.generatePartId(),
69
+ sessionID,
70
+ messageID: this.messageID,
71
+ type: 'tool',
72
+ callID,
73
+ tool: tool.name,
74
+ state: {
75
+ status: 'completed',
76
+ input: tool.params,
77
+ output: result.output,
78
+ title: result.title || `${tool.name} ${JSON.stringify(tool.params)}`,
79
+ metadata: result.metadata || {
80
+ output: result.output,
81
+ exit: result.exitCode || 0,
82
+ ...(tool.params.description && { description: tool.params.description })
83
+ },
84
+ time: {
85
+ start: startTime,
86
+ end: endTime
87
+ }
88
+ }
89
+ }
90
+ })
91
+ } catch (error) {
92
+ const errorTime = Date.now()
93
+ const callID = `call_${Math.floor(Math.random() * 100000000)}`
94
+
95
+ // Log full error to stderr for debugging
96
+ console.error('Tool execution error:', error)
97
+
98
+ // Emit tool_use event with error
99
+ this.emitEvent('tool_use', {
100
+ part: {
101
+ id: this.generatePartId(),
102
+ sessionID,
103
+ messageID: this.messageID,
104
+ type: 'tool',
105
+ callID,
106
+ tool: tool.name,
107
+ state: {
108
+ status: 'error',
109
+ input: tool.params,
110
+ error: error.message || String(error),
111
+ time: {
112
+ start: errorTime,
113
+ end: errorTime
114
+ }
115
+ }
116
+ }
117
+ })
118
+ }
119
+ }
120
+ }
121
+
122
+ // Emit step_finish for tool requests
123
+ this.emitEvent('step_finish', {
124
+ part: {
125
+ id: this.generatePartId(),
126
+ sessionID,
127
+ messageID: this.messageID,
128
+ type: 'step-finish',
129
+ reason: 'stop',
130
+ snapshot,
131
+ cost: 0,
132
+ tokens: {
133
+ input: 1273,
134
+ output: 2,
135
+ reasoning: 173,
136
+ cache: { read: 9536, write: 0 }
137
+ }
138
+ }
139
+ })
140
+
141
+ return {
142
+ sessionID,
143
+ timestamp: Date.now()
144
+ }
145
+ }
146
+
147
+ // Regular message processing
148
+ // Simulate processing delay
149
+ await new Promise(resolve => setTimeout(resolve, 100))
150
+
151
+ // Emit text response like opencode
152
+ const responseText = message === "hi" ? "Hi!" : `You said: "${message}"`
153
+ this.emitEvent('text', {
154
+ part: {
155
+ id: this.generatePartId(),
156
+ sessionID,
157
+ messageID: this.messageID,
158
+ type: 'text',
159
+ text: responseText,
160
+ time: {
161
+ start: Date.now(),
162
+ end: Date.now()
163
+ }
164
+ }
165
+ })
166
+
167
+ // Emit step_finish with cost and tokens like opencode
168
+ this.emitEvent('step_finish', {
169
+ part: {
170
+ id: this.generatePartId(),
171
+ sessionID,
172
+ messageID: this.messageID,
173
+ type: 'step-finish',
174
+ reason: 'stop',
175
+ snapshot,
176
+ cost: 0,
177
+ tokens: {
178
+ input: 1273,
179
+ output: 2,
180
+ reasoning: 173,
181
+ cache: { read: 9536, write: 0 }
182
+ }
183
+ }
184
+ })
185
+
186
+ return {
187
+ sessionID,
188
+ timestamp: Date.now()
189
+ }
190
+ }
191
+
192
+ emitEvent(type, data) {
193
+ const event = {
194
+ type,
195
+ timestamp: Date.now(),
196
+ sessionID: this.sessionID,
197
+ ...data
198
+ }
199
+ // Pretty-print JSON for human readability, compact for programmatic use
200
+ // Use AGENT_CLI_COMPACT=1 for compact output (tests, automation)
201
+ const compact = process.env.AGENT_CLI_COMPACT === '1'
202
+ console.log(compact ? JSON.stringify(event) : JSON.stringify(event, null, 2))
203
+ }
204
+ }
@@ -0,0 +1,249 @@
1
+ import { streamText, wrapLanguageModel, type ModelMessage } from "ai"
2
+ import { Session } from "."
3
+ import { Identifier } from "../id/id"
4
+ import { Instance } from "../project/instance"
5
+ import { Provider } from "../provider/provider"
6
+ import { MessageV2 } from "./message-v2"
7
+ import { SystemPrompt } from "./system"
8
+ import { Bus } from "../bus"
9
+ import z from "zod"
10
+ import type { ModelsDev } from "../provider/models"
11
+ import { SessionPrompt } from "./prompt"
12
+ import { Flag } from "../flag/flag"
13
+ import { Token } from "../util/token"
14
+ import { Log } from "../util/log"
15
+ import { ProviderTransform } from "../provider/transform"
16
+ import { SessionProcessor } from "./processor"
17
+ import { fn } from "../util/fn"
18
+
19
+ export namespace SessionCompaction {
20
+ const log = Log.create({ service: "session.compaction" })
21
+
22
+ export const Event = {
23
+ Compacted: Bus.event(
24
+ "session.compacted",
25
+ z.object({
26
+ sessionID: z.string(),
27
+ }),
28
+ ),
29
+ }
30
+
31
+ export function isOverflow(input: { tokens: MessageV2.Assistant["tokens"]; model: ModelsDev.Model }) {
32
+ if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) return false
33
+ const context = input.model.limit.context
34
+ if (context === 0) return false
35
+ const count = input.tokens.input + input.tokens.cache.read + input.tokens.output
36
+ const output = Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) || SessionPrompt.OUTPUT_TOKEN_MAX
37
+ const usable = context - output
38
+ return count > usable
39
+ }
40
+
41
+ export const PRUNE_MINIMUM = 20_000
42
+ export const PRUNE_PROTECT = 40_000
43
+
44
+ // goes backwards through parts until there are 40_000 tokens worth of tool
45
+ // calls. then erases output of previous tool calls. idea is to throw away old
46
+ // tool calls that are no longer relevant.
47
+ export async function prune(input: { sessionID: string }) {
48
+ if (Flag.OPENCODE_DISABLE_PRUNE) return
49
+ log.info("pruning")
50
+ const msgs = await Session.messages({ sessionID: input.sessionID })
51
+ let total = 0
52
+ let pruned = 0
53
+ const toPrune = []
54
+ let turns = 0
55
+
56
+ loop: for (let msgIndex = msgs.length - 1; msgIndex >= 0; msgIndex--) {
57
+ const msg = msgs[msgIndex]
58
+ if (msg.info.role === "user") turns++
59
+ if (turns < 2) continue
60
+ if (msg.info.role === "assistant" && msg.info.summary) break loop
61
+ for (let partIndex = msg.parts.length - 1; partIndex >= 0; partIndex--) {
62
+ const part = msg.parts[partIndex]
63
+ if (part.type === "tool")
64
+ if (part.state.status === "completed") {
65
+ if (part.state.time.compacted) break loop
66
+ const estimate = Token.estimate(part.state.output)
67
+ total += estimate
68
+ if (total > PRUNE_PROTECT) {
69
+ pruned += estimate
70
+ toPrune.push(part)
71
+ }
72
+ }
73
+ }
74
+ }
75
+ log.info("found", { pruned, total })
76
+ if (pruned > PRUNE_MINIMUM) {
77
+ for (const part of toPrune) {
78
+ if (part.state.status === "completed") {
79
+ part.state.time.compacted = Date.now()
80
+ await Session.updatePart(part)
81
+ }
82
+ }
83
+ log.info("pruned", { count: toPrune.length })
84
+ }
85
+ }
86
+
87
+ export async function process(input: {
88
+ parentID: string
89
+ messages: MessageV2.WithParts[]
90
+ sessionID: string
91
+ model: {
92
+ providerID: string
93
+ modelID: string
94
+ }
95
+ abort: AbortSignal
96
+ }) {
97
+ const model = await Provider.getModel(input.model.providerID, input.model.modelID)
98
+ const system = [...SystemPrompt.summarize(model.providerID)]
99
+ const msg = (await Session.updateMessage({
100
+ id: Identifier.ascending("message"),
101
+ role: "assistant",
102
+ parentID: input.parentID,
103
+ sessionID: input.sessionID,
104
+ mode: "build",
105
+ summary: true,
106
+ path: {
107
+ cwd: Instance.directory,
108
+ root: Instance.worktree,
109
+ },
110
+ cost: 0,
111
+ tokens: {
112
+ output: 0,
113
+ input: 0,
114
+ reasoning: 0,
115
+ cache: { read: 0, write: 0 },
116
+ },
117
+ modelID: input.model.modelID,
118
+ providerID: model.providerID,
119
+ time: {
120
+ created: Date.now(),
121
+ },
122
+ })) as MessageV2.Assistant
123
+ const processor = SessionProcessor.create({
124
+ assistantMessage: msg,
125
+ sessionID: input.sessionID,
126
+ providerID: input.model.providerID,
127
+ model: model.info,
128
+ abort: input.abort,
129
+ })
130
+ const result = await processor.process(() =>
131
+ streamText({
132
+ onError(error) {
133
+ log.error("stream error", {
134
+ error,
135
+ })
136
+ },
137
+ // set to 0, we handle loop
138
+ maxRetries: 0,
139
+ providerOptions: ProviderTransform.providerOptions(model.npm, model.providerID, {
140
+ ...ProviderTransform.options(model.providerID, model.modelID, model.npm ?? "", input.sessionID),
141
+ ...model.info.options,
142
+ }),
143
+ headers: model.info.headers,
144
+ abortSignal: input.abort,
145
+ tools: model.info.tool_call ? {} : undefined,
146
+ messages: [
147
+ ...system.map(
148
+ (x): ModelMessage => ({
149
+ role: "system",
150
+ content: x,
151
+ }),
152
+ ),
153
+ ...MessageV2.toModelMessage(
154
+ input.messages.filter((m) => {
155
+ if (m.info.role !== "assistant" || m.info.error === undefined) {
156
+ return true
157
+ }
158
+ if (
159
+ MessageV2.AbortedError.isInstance(m.info.error) &&
160
+ m.parts.some((part) => part.type !== "step-start" && part.type !== "reasoning")
161
+ ) {
162
+ return true
163
+ }
164
+
165
+ return false
166
+ }),
167
+ ),
168
+ {
169
+ role: "user",
170
+ content: [
171
+ {
172
+ type: "text",
173
+ text: "Provide a detailed but concise summary of our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next.",
174
+ },
175
+ ],
176
+ },
177
+ ],
178
+ model: wrapLanguageModel({
179
+ model: model.language,
180
+ middleware: [
181
+ {
182
+ async transformParams(args) {
183
+ if (args.type === "stream") {
184
+ // @ts-expect-error
185
+ args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID)
186
+ }
187
+ return args.params
188
+ },
189
+ },
190
+ ],
191
+ }),
192
+ }),
193
+ )
194
+ if (result === "continue") {
195
+ const continueMsg = await Session.updateMessage({
196
+ id: Identifier.ascending("message"),
197
+ role: "user",
198
+ sessionID: input.sessionID,
199
+ time: {
200
+ created: Date.now(),
201
+ },
202
+ agent: "build",
203
+ model: input.model,
204
+ })
205
+ await Session.updatePart({
206
+ id: Identifier.ascending("part"),
207
+ messageID: continueMsg.id,
208
+ sessionID: input.sessionID,
209
+ type: "text",
210
+ synthetic: true,
211
+ text: "Continue if you have next steps",
212
+ time: {
213
+ start: Date.now(),
214
+ end: Date.now(),
215
+ },
216
+ })
217
+ }
218
+ if (processor.message.error) return "stop"
219
+ return "continue"
220
+ }
221
+
222
+ export const create = fn(
223
+ z.object({
224
+ sessionID: Identifier.schema("session"),
225
+ model: z.object({
226
+ providerID: z.string(),
227
+ modelID: z.string(),
228
+ }),
229
+ }),
230
+ async (input) => {
231
+ const msg = await Session.updateMessage({
232
+ id: Identifier.ascending("message"),
233
+ role: "user",
234
+ model: input.model,
235
+ sessionID: input.sessionID,
236
+ agent: "build",
237
+ time: {
238
+ created: Date.now(),
239
+ },
240
+ })
241
+ await Session.updatePart({
242
+ id: Identifier.ascending("part"),
243
+ messageID: msg.id,
244
+ sessionID: msg.sessionID,
245
+ type: "compaction",
246
+ })
247
+ },
248
+ )
249
+ }