@link-assistant/agent 0.0.8 → 0.0.11

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 +80 -1
  2. package/MODELS.md +72 -24
  3. package/README.md +95 -2
  4. package/TOOLS.md +20 -0
  5. package/package.json +36 -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 +468 -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 +210 -53
  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 +78 -0
  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 +554 -332
  38. package/src/json-standard/index.ts +173 -0
  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,191 +1,225 @@
1
- import type { ModelMessage } from "ai"
2
- import { unique } from "remeda"
3
- import type { JSONSchema } from "zod/v4/core"
1
+ import type { ModelMessage } from 'ai';
2
+ import { unique } from 'remeda';
3
+ import type { JSONSchema } from 'zod/v4/core';
4
4
 
5
5
  export namespace ProviderTransform {
6
- function normalizeMessages(msgs: ModelMessage[], providerID: string, modelID: string): ModelMessage[] {
7
- if (modelID.includes("claude")) {
6
+ function normalizeMessages(
7
+ msgs: ModelMessage[],
8
+ providerID: string,
9
+ modelID: string
10
+ ): ModelMessage[] {
11
+ if (modelID.includes('claude')) {
8
12
  return msgs.map((msg) => {
9
- if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
13
+ if (
14
+ (msg.role === 'assistant' || msg.role === 'tool') &&
15
+ Array.isArray(msg.content)
16
+ ) {
10
17
  msg.content = msg.content.map((part) => {
11
- if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
18
+ if (
19
+ (part.type === 'tool-call' || part.type === 'tool-result') &&
20
+ 'toolCallId' in part
21
+ ) {
12
22
  return {
13
23
  ...part,
14
- toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, "_"),
15
- }
24
+ toolCallId: part.toolCallId.replace(/[^a-zA-Z0-9_-]/g, '_'),
25
+ };
16
26
  }
17
- return part
18
- })
27
+ return part;
28
+ });
19
29
  }
20
- return msg
21
- })
30
+ return msg;
31
+ });
22
32
  }
23
- if (providerID === "mistral" || modelID.toLowerCase().includes("mistral")) {
24
- const result: ModelMessage[] = []
33
+ if (providerID === 'mistral' || modelID.toLowerCase().includes('mistral')) {
34
+ const result: ModelMessage[] = [];
25
35
  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]
36
+ const msg = msgs[i];
37
+ const prevMsg = msgs[i - 1];
38
+ const nextMsg = msgs[i + 1];
29
39
 
30
- if ((msg.role === "assistant" || msg.role === "tool") && Array.isArray(msg.content)) {
40
+ if (
41
+ (msg.role === 'assistant' || msg.role === 'tool') &&
42
+ Array.isArray(msg.content)
43
+ ) {
31
44
  msg.content = msg.content.map((part) => {
32
- if ((part.type === "tool-call" || part.type === "tool-result") && "toolCallId" in part) {
45
+ if (
46
+ (part.type === 'tool-call' || part.type === 'tool-result') &&
47
+ 'toolCallId' in part
48
+ ) {
33
49
  // Mistral requires alphanumeric tool call IDs with exactly 9 characters
34
50
  const normalizedId = part.toolCallId
35
- .replace(/[^a-zA-Z0-9]/g, "") // Remove non-alphanumeric characters
51
+ .replace(/[^a-zA-Z0-9]/g, '') // Remove non-alphanumeric characters
36
52
  .substring(0, 9) // Take first 9 characters
37
- .padEnd(9, "0") // Pad with zeros if less than 9 characters
53
+ .padEnd(9, '0'); // Pad with zeros if less than 9 characters
38
54
 
39
55
  return {
40
56
  ...part,
41
57
  toolCallId: normalizedId,
42
- }
58
+ };
43
59
  }
44
- return part
45
- })
60
+ return part;
61
+ });
46
62
  }
47
63
 
48
- result.push(msg)
64
+ result.push(msg);
49
65
 
50
66
  // Fix message sequence: tool messages cannot be followed by user messages
51
- if (msg.role === "tool" && nextMsg?.role === "user") {
67
+ if (msg.role === 'tool' && nextMsg?.role === 'user') {
52
68
  result.push({
53
- role: "assistant",
69
+ role: 'assistant',
54
70
  content: [
55
71
  {
56
- type: "text",
57
- text: "Done.",
72
+ type: 'text',
73
+ text: 'Done.',
58
74
  },
59
75
  ],
60
- })
76
+ });
61
77
  }
62
78
  }
63
- return result
79
+ return result;
64
80
  }
65
81
 
66
- return msgs
82
+ return msgs;
67
83
  }
68
84
 
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)
85
+ function applyCaching(
86
+ msgs: ModelMessage[],
87
+ providerID: string
88
+ ): ModelMessage[] {
89
+ const system = msgs.filter((msg) => msg.role === 'system').slice(0, 2);
90
+ const final = msgs.filter((msg) => msg.role !== 'system').slice(-2);
72
91
 
73
92
  const providerOptions = {
74
93
  anthropic: {
75
- cacheControl: { type: "ephemeral" },
94
+ cacheControl: { type: 'ephemeral' },
76
95
  },
77
96
  openrouter: {
78
- cache_control: { type: "ephemeral" },
97
+ cache_control: { type: 'ephemeral' },
79
98
  },
80
99
  bedrock: {
81
- cachePoint: { type: "ephemeral" },
100
+ cachePoint: { type: 'ephemeral' },
82
101
  },
83
102
  openaiCompatible: {
84
- cache_control: { type: "ephemeral" },
103
+ cache_control: { type: 'ephemeral' },
85
104
  },
86
- }
105
+ };
87
106
 
88
107
  for (const msg of unique([...system, ...final])) {
89
- const shouldUseContentOptions = providerID !== "anthropic" && Array.isArray(msg.content) && msg.content.length > 0
108
+ const shouldUseContentOptions =
109
+ providerID !== 'anthropic' &&
110
+ Array.isArray(msg.content) &&
111
+ msg.content.length > 0;
90
112
 
91
113
  if (shouldUseContentOptions) {
92
- const lastContent = msg.content[msg.content.length - 1]
93
- if (lastContent && typeof lastContent === "object") {
114
+ const lastContent = msg.content[msg.content.length - 1];
115
+ if (lastContent && typeof lastContent === 'object') {
94
116
  lastContent.providerOptions = {
95
117
  ...lastContent.providerOptions,
96
118
  ...providerOptions,
97
- }
98
- continue
119
+ };
120
+ continue;
99
121
  }
100
122
  }
101
123
 
102
124
  msg.providerOptions = {
103
125
  ...msg.providerOptions,
104
126
  ...providerOptions,
105
- }
127
+ };
106
128
  }
107
129
 
108
- return msgs
130
+ return msgs;
109
131
  }
110
132
 
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)
133
+ export function message(
134
+ msgs: ModelMessage[],
135
+ providerID: string,
136
+ modelID: string
137
+ ) {
138
+ msgs = normalizeMessages(msgs, providerID, modelID);
139
+ if (
140
+ providerID === 'anthropic' ||
141
+ modelID.includes('anthropic') ||
142
+ modelID.includes('claude')
143
+ ) {
144
+ msgs = applyCaching(msgs, providerID);
115
145
  }
116
146
 
117
- return msgs
147
+ return msgs;
118
148
  }
119
149
 
120
150
  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
151
+ if (modelID.toLowerCase().includes('qwen')) return 0.55;
152
+ if (modelID.toLowerCase().includes('claude')) return undefined;
153
+ if (modelID.toLowerCase().includes('gemini-3-pro')) return 1.0;
154
+ return 0;
125
155
  }
126
156
 
127
157
  export function topP(_providerID: string, modelID: string) {
128
- if (modelID.toLowerCase().includes("qwen")) return 1
129
- return undefined
158
+ if (modelID.toLowerCase().includes('qwen')) return 1;
159
+ return undefined;
130
160
  }
131
161
 
132
162
  export function options(
133
163
  providerID: string,
134
164
  modelID: string,
135
165
  npm: string,
136
- sessionID: string,
166
+ sessionID: string
137
167
  ): Record<string, any> | undefined {
138
- const result: Record<string, any> = {}
168
+ const result: Record<string, any> = {};
139
169
 
140
- if (providerID === "openai") {
141
- result["promptCacheKey"] = sessionID
170
+ if (providerID === 'openai') {
171
+ result['promptCacheKey'] = sessionID;
142
172
  }
143
173
 
144
- if (modelID.includes("gpt-5") && !modelID.includes("gpt-5-chat")) {
145
- if (modelID.includes("codex")) {
146
- result["store"] = false
174
+ if (modelID.includes('gpt-5') && !modelID.includes('gpt-5-chat')) {
175
+ if (modelID.includes('codex')) {
176
+ result['store'] = false;
147
177
  }
148
178
 
149
- if (!modelID.includes("codex") && !modelID.includes("gpt-5-pro")) {
150
- result["reasoningEffort"] = "medium"
179
+ if (!modelID.includes('codex') && !modelID.includes('gpt-5-pro')) {
180
+ result['reasoningEffort'] = 'medium';
151
181
  }
152
182
 
153
- if (modelID.endsWith("gpt-5.1") && providerID !== "azure") {
154
- result["textVerbosity"] = "low"
183
+ if (modelID.endsWith('gpt-5.1') && providerID !== 'azure') {
184
+ result['textVerbosity'] = 'low';
155
185
  }
156
186
 
157
- if (providerID === "opencode") {
158
- result["promptCacheKey"] = sessionID
159
- result["include"] = ["reasoning.encrypted_content"]
160
- result["reasoningSummary"] = "auto"
187
+ if (providerID === 'opencode') {
188
+ result['promptCacheKey'] = sessionID;
189
+ result['include'] = ['reasoning.encrypted_content'];
190
+ result['reasoningSummary'] = 'auto';
161
191
  }
162
192
  }
163
- return result
193
+ return result;
164
194
  }
165
195
 
166
- export function providerOptions(npm: string | undefined, providerID: string, options: { [x: string]: any }) {
196
+ export function providerOptions(
197
+ npm: string | undefined,
198
+ providerID: string,
199
+ options: { [x: string]: any }
200
+ ) {
167
201
  switch (npm) {
168
- case "@ai-sdk/openai":
169
- case "@ai-sdk/azure":
202
+ case '@ai-sdk/openai':
203
+ case '@ai-sdk/azure':
170
204
  return {
171
- ["openai" as string]: options,
172
- }
173
- case "@ai-sdk/amazon-bedrock":
205
+ ['openai' as string]: options,
206
+ };
207
+ case '@ai-sdk/amazon-bedrock':
174
208
  return {
175
- ["bedrock" as string]: options,
176
- }
177
- case "@ai-sdk/anthropic":
209
+ ['bedrock' as string]: options,
210
+ };
211
+ case '@ai-sdk/anthropic':
178
212
  return {
179
- ["anthropic" as string]: options,
180
- }
181
- case "@ai-sdk/gateway":
213
+ ['anthropic' as string]: options,
214
+ };
215
+ case '@ai-sdk/gateway':
182
216
  return {
183
- ["gateway" as string]: options,
184
- }
217
+ ['gateway' as string]: options,
218
+ };
185
219
  default:
186
220
  return {
187
221
  [providerID]: options,
188
- }
222
+ };
189
223
  }
190
224
  }
191
225
 
@@ -193,28 +227,35 @@ export namespace ProviderTransform {
193
227
  npm: string,
194
228
  options: Record<string, any>,
195
229
  modelLimit: number,
196
- globalLimit: number,
230
+ globalLimit: number
197
231
  ): number {
198
- const modelCap = modelLimit || globalLimit
199
- const standardLimit = Math.min(modelCap, globalLimit)
232
+ const modelCap = modelLimit || globalLimit;
233
+ const standardLimit = Math.min(modelCap, globalLimit);
200
234
 
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"
235
+ if (npm === '@ai-sdk/anthropic') {
236
+ const thinking = options?.['thinking'];
237
+ const budgetTokens =
238
+ typeof thinking?.['budgetTokens'] === 'number'
239
+ ? thinking['budgetTokens']
240
+ : 0;
241
+ const enabled = thinking?.['type'] === 'enabled';
205
242
  if (enabled && budgetTokens > 0) {
206
243
  // Return text tokens so that text + thinking <= model cap, preferring 32k text when possible.
207
244
  if (budgetTokens + standardLimit <= modelCap) {
208
- return standardLimit
245
+ return standardLimit;
209
246
  }
210
- return modelCap - budgetTokens
247
+ return modelCap - budgetTokens;
211
248
  }
212
249
  }
213
250
 
214
- return standardLimit
251
+ return standardLimit;
215
252
  }
216
253
 
217
- export function schema(_providerID: string, _modelID: string, schema: JSONSchema.BaseSchema) {
254
+ export function schema(
255
+ _providerID: string,
256
+ _modelID: string,
257
+ schema: JSONSchema.BaseSchema
258
+ ) {
218
259
  /*
219
260
  if (["openai", "azure"].includes(providerID)) {
220
261
  if (schema.type === "object" && schema.properties) {
@@ -236,6 +277,6 @@ export namespace ProviderTransform {
236
277
  }
237
278
  */
238
279
 
239
- return schema
280
+ return schema;
240
281
  }
241
282
  }
@@ -1,20 +1,20 @@
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"
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
6
 
7
7
  export const ProjectRoute = new Hono()
8
8
  .get(
9
- "/",
9
+ '/',
10
10
  describeRoute({
11
- description: "List all projects",
12
- operationId: "project.list",
11
+ description: 'List all projects',
12
+ operationId: 'project.list',
13
13
  responses: {
14
14
  200: {
15
- description: "List of projects",
15
+ description: 'List of projects',
16
16
  content: {
17
- "application/json": {
17
+ 'application/json': {
18
18
  schema: resolver(Project.Info.array()),
19
19
  },
20
20
  },
@@ -22,20 +22,20 @@ export const ProjectRoute = new Hono()
22
22
  },
23
23
  }),
24
24
  async (c) => {
25
- const projects = await Project.list()
26
- return c.json(projects)
27
- },
25
+ const projects = await Project.list();
26
+ return c.json(projects);
27
+ }
28
28
  )
29
29
  .get(
30
- "/current",
30
+ '/current',
31
31
  describeRoute({
32
- description: "Get the current project",
33
- operationId: "project.current",
32
+ description: 'Get the current project',
33
+ operationId: 'project.current',
34
34
  responses: {
35
35
  200: {
36
- description: "Current project",
36
+ description: 'Current project',
37
37
  content: {
38
- "application/json": {
38
+ 'application/json': {
39
39
  schema: resolver(Project.Info),
40
40
  },
41
41
  },
@@ -43,6 +43,6 @@ export const ProjectRoute = new Hono()
43
43
  },
44
44
  }),
45
45
  async (c) => {
46
- return c.json(Instance.project)
47
- },
48
- )
46
+ return c.json(Instance.project);
47
+ }
48
+ );