@link-assistant/agent 0.0.9 → 0.0.12

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 (104) hide show
  1. package/EXAMPLES.md +36 -0
  2. package/MODELS.md +72 -24
  3. package/README.md +59 -2
  4. package/TOOLS.md +20 -0
  5. package/package.json +35 -2
  6. package/src/agent/agent.ts +68 -54
  7. package/src/auth/claude-oauth.ts +426 -0
  8. package/src/auth/index.ts +28 -26
  9. package/src/auth/plugins.ts +876 -0
  10. package/src/bun/index.ts +53 -43
  11. package/src/bus/global.ts +5 -5
  12. package/src/bus/index.ts +59 -53
  13. package/src/cli/bootstrap.js +12 -12
  14. package/src/cli/bootstrap.ts +6 -6
  15. package/src/cli/cmd/agent.ts +97 -92
  16. package/src/cli/cmd/auth.ts +469 -0
  17. package/src/cli/cmd/cmd.ts +2 -2
  18. package/src/cli/cmd/export.ts +41 -41
  19. package/src/cli/cmd/mcp.ts +144 -119
  20. package/src/cli/cmd/models.ts +30 -29
  21. package/src/cli/cmd/run.ts +269 -213
  22. package/src/cli/cmd/stats.ts +185 -146
  23. package/src/cli/error.ts +17 -13
  24. package/src/cli/ui.ts +39 -24
  25. package/src/command/index.ts +26 -26
  26. package/src/config/config.ts +528 -288
  27. package/src/config/markdown.ts +15 -15
  28. package/src/file/ripgrep.ts +201 -169
  29. package/src/file/time.ts +21 -18
  30. package/src/file/watcher.ts +51 -42
  31. package/src/file.ts +1 -1
  32. package/src/flag/flag.ts +26 -11
  33. package/src/format/formatter.ts +206 -162
  34. package/src/format/index.ts +61 -61
  35. package/src/global/index.ts +21 -21
  36. package/src/id/id.ts +47 -33
  37. package/src/index.js +346 -199
  38. package/src/json-standard/index.ts +67 -51
  39. package/src/mcp/index.ts +135 -128
  40. package/src/patch/index.ts +336 -267
  41. package/src/project/bootstrap.ts +15 -15
  42. package/src/project/instance.ts +43 -36
  43. package/src/project/project.ts +47 -47
  44. package/src/project/state.ts +37 -33
  45. package/src/provider/models-macro.ts +5 -5
  46. package/src/provider/models.ts +32 -32
  47. package/src/provider/opencode.js +19 -19
  48. package/src/provider/provider.ts +518 -277
  49. package/src/provider/transform.ts +143 -102
  50. package/src/server/project.ts +21 -21
  51. package/src/server/server.ts +111 -105
  52. package/src/session/agent.js +66 -60
  53. package/src/session/compaction.ts +136 -111
  54. package/src/session/index.ts +189 -156
  55. package/src/session/message-v2.ts +312 -268
  56. package/src/session/message.ts +73 -57
  57. package/src/session/processor.ts +180 -166
  58. package/src/session/prompt.ts +678 -533
  59. package/src/session/retry.ts +26 -23
  60. package/src/session/revert.ts +76 -62
  61. package/src/session/status.ts +26 -26
  62. package/src/session/summary.ts +97 -76
  63. package/src/session/system.ts +77 -63
  64. package/src/session/todo.ts +22 -16
  65. package/src/snapshot/index.ts +92 -76
  66. package/src/storage/storage.ts +157 -120
  67. package/src/tool/bash.ts +116 -106
  68. package/src/tool/batch.ts +73 -59
  69. package/src/tool/codesearch.ts +60 -53
  70. package/src/tool/edit.ts +319 -263
  71. package/src/tool/glob.ts +32 -28
  72. package/src/tool/grep.ts +72 -53
  73. package/src/tool/invalid.ts +7 -7
  74. package/src/tool/ls.ts +77 -64
  75. package/src/tool/multiedit.ts +30 -21
  76. package/src/tool/patch.ts +121 -94
  77. package/src/tool/read.ts +140 -122
  78. package/src/tool/registry.ts +38 -38
  79. package/src/tool/task.ts +93 -60
  80. package/src/tool/todo.ts +16 -16
  81. package/src/tool/tool.ts +45 -36
  82. package/src/tool/webfetch.ts +97 -74
  83. package/src/tool/websearch.ts +78 -64
  84. package/src/tool/write.ts +21 -15
  85. package/src/util/binary.ts +27 -19
  86. package/src/util/context.ts +8 -8
  87. package/src/util/defer.ts +7 -5
  88. package/src/util/error.ts +24 -19
  89. package/src/util/eventloop.ts +16 -10
  90. package/src/util/filesystem.ts +37 -33
  91. package/src/util/fn.ts +11 -8
  92. package/src/util/iife.ts +1 -1
  93. package/src/util/keybind.ts +44 -44
  94. package/src/util/lazy.ts +7 -7
  95. package/src/util/locale.ts +20 -16
  96. package/src/util/lock.ts +43 -38
  97. package/src/util/log.ts +95 -85
  98. package/src/util/queue.ts +8 -8
  99. package/src/util/rpc.ts +35 -23
  100. package/src/util/scrap.ts +4 -4
  101. package/src/util/signal.ts +5 -5
  102. package/src/util/timeout.ts +6 -6
  103. package/src/util/token.ts +2 -2
  104. package/src/util/wildcard.ts +38 -27
@@ -1,164 +1,170 @@
1
- import type { ModelsDev } from "../provider/models"
2
- import { MessageV2 } from "./message-v2"
3
- import { type StreamTextResult, type Tool as AITool, APICallError } from "ai"
4
- import { Log } from "../util/log"
5
- import { Identifier } from "../id/id"
6
- import { Session } from "."
7
- import { Agent } from "../agent/agent"
1
+ import type { ModelsDev } from '../provider/models';
2
+ import { MessageV2 } from './message-v2';
3
+ import { type StreamTextResult, type Tool as AITool, APICallError } from 'ai';
4
+ import { Log } from '../util/log';
5
+ import { Identifier } from '../id/id';
6
+ import { Session } from '.';
7
+ import { Agent } from '../agent/agent';
8
8
  // Permission system removed - no restrictions
9
- import { Snapshot } from "../snapshot"
10
- import { SessionSummary } from "./summary"
11
- import { Bus } from "../bus"
12
- import { SessionRetry } from "./retry"
13
- import { SessionStatus } from "./status"
9
+ import { Snapshot } from '../snapshot';
10
+ import { SessionSummary } from './summary';
11
+ import { Bus } from '../bus';
12
+ import { SessionRetry } from './retry';
13
+ import { SessionStatus } from './status';
14
14
 
15
15
  export namespace SessionProcessor {
16
- const DOOM_LOOP_THRESHOLD = 3
17
- const log = Log.create({ service: "session.processor" })
16
+ const DOOM_LOOP_THRESHOLD = 3;
17
+ const log = Log.create({ service: 'session.processor' });
18
18
 
19
- export type Info = Awaited<ReturnType<typeof create>>
20
- export type Result = Awaited<ReturnType<Info["process"]>>
19
+ export type Info = Awaited<ReturnType<typeof create>>;
20
+ export type Result = Awaited<ReturnType<Info['process']>>;
21
21
 
22
22
  export function create(input: {
23
- assistantMessage: MessageV2.Assistant
24
- sessionID: string
25
- providerID: string
26
- model: ModelsDev.Model
27
- abort: AbortSignal
23
+ assistantMessage: MessageV2.Assistant;
24
+ sessionID: string;
25
+ providerID: string;
26
+ model: ModelsDev.Model;
27
+ abort: AbortSignal;
28
28
  }) {
29
- const toolcalls: Record<string, MessageV2.ToolPart> = {}
30
- let snapshot: string | undefined
31
- let blocked = false
32
- let attempt = 0
29
+ const toolcalls: Record<string, MessageV2.ToolPart> = {};
30
+ let snapshot: string | undefined;
31
+ let blocked = false;
32
+ let attempt = 0;
33
33
 
34
34
  const result = {
35
35
  get message() {
36
- return input.assistantMessage
36
+ return input.assistantMessage;
37
37
  },
38
38
  partFromToolCall(toolCallID: string) {
39
- return toolcalls[toolCallID]
39
+ return toolcalls[toolCallID];
40
40
  },
41
41
  async process(fn: () => StreamTextResult<Record<string, AITool>, never>) {
42
- log.info("process")
42
+ log.info('process');
43
43
  while (true) {
44
44
  try {
45
- let currentText: MessageV2.TextPart | undefined
46
- let reasoningMap: Record<string, MessageV2.ReasoningPart> = {}
47
- const stream = fn()
45
+ let currentText: MessageV2.TextPart | undefined;
46
+ let reasoningMap: Record<string, MessageV2.ReasoningPart> = {};
47
+ const stream = fn();
48
48
 
49
49
  for await (const value of stream.fullStream) {
50
- input.abort.throwIfAborted()
50
+ input.abort.throwIfAborted();
51
51
  switch (value.type) {
52
- case "start":
53
- SessionStatus.set(input.sessionID, { type: "busy" })
54
- break
52
+ case 'start':
53
+ SessionStatus.set(input.sessionID, { type: 'busy' });
54
+ break;
55
55
 
56
- case "reasoning-start":
56
+ case 'reasoning-start':
57
57
  if (value.id in reasoningMap) {
58
- continue
58
+ continue;
59
59
  }
60
60
  reasoningMap[value.id] = {
61
- id: Identifier.ascending("part"),
61
+ id: Identifier.ascending('part'),
62
62
  messageID: input.assistantMessage.id,
63
63
  sessionID: input.assistantMessage.sessionID,
64
- type: "reasoning",
65
- text: "",
64
+ type: 'reasoning',
65
+ text: '',
66
66
  time: {
67
67
  start: Date.now(),
68
68
  },
69
69
  metadata: value.providerMetadata,
70
- }
71
- break
70
+ };
71
+ break;
72
72
 
73
- case "reasoning-delta":
73
+ case 'reasoning-delta':
74
74
  if (value.id in reasoningMap) {
75
- const part = reasoningMap[value.id]
76
- part.text += value.text
77
- if (value.providerMetadata) part.metadata = value.providerMetadata
78
- if (part.text) await Session.updatePart({ part, delta: value.text })
75
+ const part = reasoningMap[value.id];
76
+ part.text += value.text;
77
+ if (value.providerMetadata)
78
+ part.metadata = value.providerMetadata;
79
+ if (part.text)
80
+ await Session.updatePart({ part, delta: value.text });
79
81
  }
80
- break
82
+ break;
81
83
 
82
- case "reasoning-end":
84
+ case 'reasoning-end':
83
85
  if (value.id in reasoningMap) {
84
- const part = reasoningMap[value.id]
85
- part.text = part.text.trimEnd()
86
+ const part = reasoningMap[value.id];
87
+ part.text = part.text.trimEnd();
86
88
 
87
89
  part.time = {
88
90
  ...part.time,
89
91
  end: Date.now(),
90
- }
91
- if (value.providerMetadata) part.metadata = value.providerMetadata
92
- await Session.updatePart(part)
93
- delete reasoningMap[value.id]
92
+ };
93
+ if (value.providerMetadata)
94
+ part.metadata = value.providerMetadata;
95
+ await Session.updatePart(part);
96
+ delete reasoningMap[value.id];
94
97
  }
95
- break
98
+ break;
96
99
 
97
- case "tool-input-start":
100
+ case 'tool-input-start':
98
101
  const part = await Session.updatePart({
99
- id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
102
+ id: toolcalls[value.id]?.id ?? Identifier.ascending('part'),
100
103
  messageID: input.assistantMessage.id,
101
104
  sessionID: input.assistantMessage.sessionID,
102
- type: "tool",
105
+ type: 'tool',
103
106
  tool: value.toolName,
104
107
  callID: value.id,
105
108
  state: {
106
- status: "pending",
109
+ status: 'pending',
107
110
  input: {},
108
- raw: "",
111
+ raw: '',
109
112
  },
110
- })
111
- toolcalls[value.id] = part as MessageV2.ToolPart
112
- break
113
+ });
114
+ toolcalls[value.id] = part as MessageV2.ToolPart;
115
+ break;
113
116
 
114
- case "tool-input-delta":
115
- break
117
+ case 'tool-input-delta':
118
+ break;
116
119
 
117
- case "tool-input-end":
118
- break
120
+ case 'tool-input-end':
121
+ break;
119
122
 
120
- case "tool-call": {
121
- const match = toolcalls[value.toolCallId]
123
+ case 'tool-call': {
124
+ const match = toolcalls[value.toolCallId];
122
125
  if (match) {
123
126
  const part = await Session.updatePart({
124
127
  ...match,
125
128
  tool: value.toolName,
126
129
  state: {
127
- status: "running",
130
+ status: 'running',
128
131
  input: value.input,
129
132
  time: {
130
133
  start: Date.now(),
131
134
  },
132
135
  },
133
136
  metadata: value.providerMetadata,
134
- })
135
- toolcalls[value.toolCallId] = part as MessageV2.ToolPart
137
+ });
138
+ toolcalls[value.toolCallId] = part as MessageV2.ToolPart;
136
139
 
137
- const parts = await MessageV2.parts(input.assistantMessage.id)
138
- const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD)
140
+ const parts = await MessageV2.parts(
141
+ input.assistantMessage.id
142
+ );
143
+ const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD);
139
144
 
140
145
  if (
141
146
  lastThree.length === DOOM_LOOP_THRESHOLD &&
142
147
  lastThree.every(
143
148
  (p) =>
144
- p.type === "tool" &&
149
+ p.type === 'tool' &&
145
150
  p.tool === value.toolName &&
146
- p.state.status !== "pending" &&
147
- JSON.stringify(p.state.input) === JSON.stringify(value.input),
151
+ p.state.status !== 'pending' &&
152
+ JSON.stringify(p.state.input) ===
153
+ JSON.stringify(value.input)
148
154
  )
149
155
  ) {
150
156
  // Permission system removed - doom loop detection disabled
151
157
  }
152
158
  }
153
- break
159
+ break;
154
160
  }
155
- case "tool-result": {
156
- const match = toolcalls[value.toolCallId]
157
- if (match && match.state.status === "running") {
161
+ case 'tool-result': {
162
+ const match = toolcalls[value.toolCallId];
163
+ if (match && match.state.status === 'running') {
158
164
  await Session.updatePart({
159
165
  ...match,
160
166
  state: {
161
- status: "completed",
167
+ status: 'completed',
162
168
  input: value.input,
163
169
  output: value.output.output,
164
170
  metadata: value.output.metadata,
@@ -169,20 +175,20 @@ export namespace SessionProcessor {
169
175
  },
170
176
  attachments: value.output.attachments,
171
177
  },
172
- })
178
+ });
173
179
 
174
- delete toolcalls[value.toolCallId]
180
+ delete toolcalls[value.toolCallId];
175
181
  }
176
- break
182
+ break;
177
183
  }
178
184
 
179
- case "tool-error": {
180
- const match = toolcalls[value.toolCallId]
181
- if (match && match.state.status === "running") {
185
+ case 'tool-error': {
186
+ const match = toolcalls[value.toolCallId];
187
+ if (match && match.state.status === 'running') {
182
188
  await Session.updatePart({
183
189
  ...match,
184
190
  state: {
185
- status: "error",
191
+ status: 'error',
186
192
  input: value.input,
187
193
  error: (value.error as any).toString(),
188
194
  metadata: undefined,
@@ -191,166 +197,174 @@ export namespace SessionProcessor {
191
197
  end: Date.now(),
192
198
  },
193
199
  },
194
- })
200
+ });
195
201
 
196
202
  // Permission system removed
197
- delete toolcalls[value.toolCallId]
203
+ delete toolcalls[value.toolCallId];
198
204
  }
199
- break
205
+ break;
200
206
  }
201
- case "error":
202
- throw value.error
207
+ case 'error':
208
+ throw value.error;
203
209
 
204
- case "start-step":
205
- snapshot = await Snapshot.track()
210
+ case 'start-step':
211
+ snapshot = await Snapshot.track();
206
212
  await Session.updatePart({
207
- id: Identifier.ascending("part"),
213
+ id: Identifier.ascending('part'),
208
214
  messageID: input.assistantMessage.id,
209
215
  sessionID: input.sessionID,
210
216
  snapshot,
211
- type: "step-start",
212
- })
213
- break
217
+ type: 'step-start',
218
+ });
219
+ break;
214
220
 
215
- case "finish-step":
221
+ case 'finish-step':
216
222
  const usage = Session.getUsage({
217
223
  model: input.model,
218
224
  usage: value.usage,
219
225
  metadata: value.providerMetadata,
220
- })
221
- input.assistantMessage.finish = value.finishReason
222
- input.assistantMessage.cost += usage.cost
223
- input.assistantMessage.tokens = usage.tokens
226
+ });
227
+ input.assistantMessage.finish = value.finishReason;
228
+ input.assistantMessage.cost += usage.cost;
229
+ input.assistantMessage.tokens = usage.tokens;
224
230
  await Session.updatePart({
225
- id: Identifier.ascending("part"),
231
+ id: Identifier.ascending('part'),
226
232
  reason: value.finishReason,
227
233
  snapshot: await Snapshot.track(),
228
234
  messageID: input.assistantMessage.id,
229
235
  sessionID: input.assistantMessage.sessionID,
230
- type: "step-finish",
236
+ type: 'step-finish',
231
237
  tokens: usage.tokens,
232
238
  cost: usage.cost,
233
- })
234
- await Session.updateMessage(input.assistantMessage)
239
+ });
240
+ await Session.updateMessage(input.assistantMessage);
235
241
  if (snapshot) {
236
- const patch = await Snapshot.patch(snapshot)
242
+ const patch = await Snapshot.patch(snapshot);
237
243
  if (patch.files.length) {
238
244
  await Session.updatePart({
239
- id: Identifier.ascending("part"),
245
+ id: Identifier.ascending('part'),
240
246
  messageID: input.assistantMessage.id,
241
247
  sessionID: input.sessionID,
242
- type: "patch",
248
+ type: 'patch',
243
249
  hash: patch.hash,
244
250
  files: patch.files,
245
- })
251
+ });
246
252
  }
247
- snapshot = undefined
253
+ snapshot = undefined;
248
254
  }
249
255
  SessionSummary.summarize({
250
256
  sessionID: input.sessionID,
251
257
  messageID: input.assistantMessage.parentID,
252
- })
253
- break
258
+ });
259
+ break;
254
260
 
255
- case "text-start":
261
+ case 'text-start':
256
262
  currentText = {
257
- id: Identifier.ascending("part"),
263
+ id: Identifier.ascending('part'),
258
264
  messageID: input.assistantMessage.id,
259
265
  sessionID: input.assistantMessage.sessionID,
260
- type: "text",
261
- text: "",
266
+ type: 'text',
267
+ text: '',
262
268
  time: {
263
269
  start: Date.now(),
264
270
  },
265
271
  metadata: value.providerMetadata,
266
- }
267
- break
272
+ };
273
+ break;
268
274
 
269
- case "text-delta":
275
+ case 'text-delta':
270
276
  if (currentText) {
271
- currentText.text += value.text
272
- if (value.providerMetadata) currentText.metadata = value.providerMetadata
277
+ currentText.text += value.text;
278
+ if (value.providerMetadata)
279
+ currentText.metadata = value.providerMetadata;
273
280
  if (currentText.text)
274
281
  await Session.updatePart({
275
282
  part: currentText,
276
283
  delta: value.text,
277
- })
284
+ });
278
285
  }
279
- break
286
+ break;
280
287
 
281
- case "text-end":
288
+ case 'text-end':
282
289
  if (currentText) {
283
- currentText.text = currentText.text.trimEnd()
290
+ currentText.text = currentText.text.trimEnd();
284
291
  currentText.time = {
285
292
  start: Date.now(),
286
293
  end: Date.now(),
287
- }
288
- if (value.providerMetadata) currentText.metadata = value.providerMetadata
289
- await Session.updatePart(currentText)
294
+ };
295
+ if (value.providerMetadata)
296
+ currentText.metadata = value.providerMetadata;
297
+ await Session.updatePart(currentText);
290
298
  }
291
- currentText = undefined
292
- break
299
+ currentText = undefined;
300
+ break;
293
301
 
294
- case "finish":
295
- input.assistantMessage.time.completed = Date.now()
296
- await Session.updateMessage(input.assistantMessage)
297
- break
302
+ case 'finish':
303
+ input.assistantMessage.time.completed = Date.now();
304
+ await Session.updateMessage(input.assistantMessage);
305
+ break;
298
306
 
299
307
  default:
300
- log.info("unhandled", {
308
+ log.info('unhandled', {
301
309
  ...value,
302
- })
303
- continue
310
+ });
311
+ continue;
304
312
  }
305
313
  }
306
314
  } catch (e) {
307
- log.error("process", {
315
+ log.error('process', {
308
316
  error: e,
309
- })
310
- const error = MessageV2.fromError(e, { providerID: input.providerID })
311
- if (error?.name === "APIError" && error.data.isRetryable) {
312
- attempt++
313
- const delay = SessionRetry.delay(error, attempt)
317
+ });
318
+ const error = MessageV2.fromError(e, {
319
+ providerID: input.providerID,
320
+ });
321
+ if (error?.name === 'APIError' && error.data.isRetryable) {
322
+ attempt++;
323
+ const delay = SessionRetry.delay(error, attempt);
314
324
  SessionStatus.set(input.sessionID, {
315
- type: "retry",
325
+ type: 'retry',
316
326
  attempt,
317
327
  message: error.data.message,
318
328
  next: Date.now() + delay,
319
- })
320
- await SessionRetry.sleep(delay, input.abort).catch(() => {})
321
- continue
329
+ });
330
+ await SessionRetry.sleep(delay, input.abort).catch(() => {});
331
+ continue;
322
332
  }
323
- input.assistantMessage.error = error
333
+ input.assistantMessage.error = error;
324
334
  Bus.publish(Session.Event.Error, {
325
335
  sessionID: input.assistantMessage.sessionID,
326
336
  error: input.assistantMessage.error,
327
- })
337
+ });
328
338
  }
329
- const p = await MessageV2.parts(input.assistantMessage.id)
339
+ const p = await MessageV2.parts(input.assistantMessage.id);
330
340
  for (const part of p) {
331
- if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
341
+ if (
342
+ part.type === 'tool' &&
343
+ part.state.status !== 'completed' &&
344
+ part.state.status !== 'error'
345
+ ) {
332
346
  await Session.updatePart({
333
347
  ...part,
334
348
  state: {
335
349
  ...part.state,
336
- status: "error",
337
- error: "Tool execution aborted",
350
+ status: 'error',
351
+ error: 'Tool execution aborted',
338
352
  time: {
339
353
  start: Date.now(),
340
354
  end: Date.now(),
341
355
  },
342
356
  },
343
- })
357
+ });
344
358
  }
345
359
  }
346
- input.assistantMessage.time.completed = Date.now()
347
- await Session.updateMessage(input.assistantMessage)
348
- if (blocked) return "stop"
349
- if (input.assistantMessage.error) return "stop"
350
- return "continue"
360
+ input.assistantMessage.time.completed = Date.now();
361
+ await Session.updateMessage(input.assistantMessage);
362
+ if (blocked) return 'stop';
363
+ if (input.assistantMessage.error) return 'stop';
364
+ return 'continue';
351
365
  }
352
366
  },
353
- }
354
- return result
367
+ };
368
+ return result;
355
369
  }
356
370
  }