@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,53 +1,56 @@
1
- import { iife } from "../util/iife"
2
- import { MessageV2 } from "./message-v2"
1
+ import { iife } from '../util/iife';
2
+ import { MessageV2 } from './message-v2';
3
3
 
4
4
  export namespace SessionRetry {
5
- export const RETRY_INITIAL_DELAY = 2000
6
- export const RETRY_BACKOFF_FACTOR = 2
7
- export const RETRY_MAX_DELAY_NO_HEADERS = 30_000 // 30 seconds
5
+ export const RETRY_INITIAL_DELAY = 2000;
6
+ export const RETRY_BACKOFF_FACTOR = 2;
7
+ export const RETRY_MAX_DELAY_NO_HEADERS = 30_000; // 30 seconds
8
8
 
9
9
  export async function sleep(ms: number, signal: AbortSignal): Promise<void> {
10
10
  return new Promise((resolve, reject) => {
11
- const timeout = setTimeout(resolve, ms)
11
+ const timeout = setTimeout(resolve, ms);
12
12
  signal.addEventListener(
13
- "abort",
13
+ 'abort',
14
14
  () => {
15
- clearTimeout(timeout)
16
- reject(new DOMException("Aborted", "AbortError"))
15
+ clearTimeout(timeout);
16
+ reject(new DOMException('Aborted', 'AbortError'));
17
17
  },
18
- { once: true },
19
- )
20
- })
18
+ { once: true }
19
+ );
20
+ });
21
21
  }
22
22
 
23
23
  export function delay(error: MessageV2.APIError, attempt: number) {
24
- const headers = error.data.responseHeaders
24
+ const headers = error.data.responseHeaders;
25
25
  if (headers) {
26
- const retryAfterMs = headers["retry-after-ms"]
26
+ const retryAfterMs = headers['retry-after-ms'];
27
27
  if (retryAfterMs) {
28
- const parsedMs = Number.parseFloat(retryAfterMs)
28
+ const parsedMs = Number.parseFloat(retryAfterMs);
29
29
  if (!Number.isNaN(parsedMs)) {
30
- return parsedMs
30
+ return parsedMs;
31
31
  }
32
32
  }
33
33
 
34
- const retryAfter = headers["retry-after"]
34
+ const retryAfter = headers['retry-after'];
35
35
  if (retryAfter) {
36
- const parsedSeconds = Number.parseFloat(retryAfter)
36
+ const parsedSeconds = Number.parseFloat(retryAfter);
37
37
  if (!Number.isNaN(parsedSeconds)) {
38
38
  // convert seconds to milliseconds
39
- return Math.ceil(parsedSeconds * 1000)
39
+ return Math.ceil(parsedSeconds * 1000);
40
40
  }
41
41
  // Try parsing as HTTP date format
42
- const parsed = Date.parse(retryAfter) - Date.now()
42
+ const parsed = Date.parse(retryAfter) - Date.now();
43
43
  if (!Number.isNaN(parsed) && parsed > 0) {
44
- return Math.ceil(parsed)
44
+ return Math.ceil(parsed);
45
45
  }
46
46
  }
47
47
 
48
- return RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1)
48
+ return RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1);
49
49
  }
50
50
 
51
- return Math.min(RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1), RETRY_MAX_DELAY_NO_HEADERS)
51
+ return Math.min(
52
+ RETRY_INITIAL_DELAY * Math.pow(RETRY_BACKOFF_FACTOR, attempt - 1),
53
+ RETRY_MAX_DELAY_NO_HEADERS
54
+ );
52
55
  }
53
56
  }
@@ -1,108 +1,122 @@
1
- import z from "zod"
2
- import { Identifier } from "../id/id"
3
- import { Snapshot } from "../snapshot"
4
- import { MessageV2 } from "./message-v2"
5
- import { Session } from "."
6
- import { Log } from "../util/log"
7
- import { splitWhen } from "remeda"
8
- import { Storage } from "../storage/storage"
9
- import { Bus } from "../bus"
10
- import { SessionPrompt } from "./prompt"
1
+ import z from 'zod';
2
+ import { Identifier } from '../id/id';
3
+ import { Snapshot } from '../snapshot';
4
+ import { MessageV2 } from './message-v2';
5
+ import { Session } from '.';
6
+ import { Log } from '../util/log';
7
+ import { splitWhen } from 'remeda';
8
+ import { Storage } from '../storage/storage';
9
+ import { Bus } from '../bus';
10
+ import { SessionPrompt } from './prompt';
11
11
 
12
12
  export namespace SessionRevert {
13
- const log = Log.create({ service: "session.revert" })
13
+ const log = Log.create({ service: 'session.revert' });
14
14
 
15
15
  export const RevertInput = z.object({
16
- sessionID: Identifier.schema("session"),
17
- messageID: Identifier.schema("message"),
18
- partID: Identifier.schema("part").optional(),
19
- })
20
- export type RevertInput = z.infer<typeof RevertInput>
16
+ sessionID: Identifier.schema('session'),
17
+ messageID: Identifier.schema('message'),
18
+ partID: Identifier.schema('part').optional(),
19
+ });
20
+ export type RevertInput = z.infer<typeof RevertInput>;
21
21
 
22
22
  export async function revert(input: RevertInput) {
23
- SessionPrompt.assertNotBusy(input.sessionID)
24
- const all = await Session.messages({ sessionID: input.sessionID })
25
- let lastUser: MessageV2.User | undefined
26
- const session = await Session.get(input.sessionID)
23
+ SessionPrompt.assertNotBusy(input.sessionID);
24
+ const all = await Session.messages({ sessionID: input.sessionID });
25
+ let lastUser: MessageV2.User | undefined;
26
+ const session = await Session.get(input.sessionID);
27
27
 
28
- let revert: Session.Info["revert"]
29
- const patches: Snapshot.Patch[] = []
28
+ let revert: Session.Info['revert'];
29
+ const patches: Snapshot.Patch[] = [];
30
30
  for (const msg of all) {
31
- if (msg.info.role === "user") lastUser = msg.info
32
- const remaining = []
31
+ if (msg.info.role === 'user') lastUser = msg.info;
32
+ const remaining = [];
33
33
  for (const part of msg.parts) {
34
34
  if (revert) {
35
- if (part.type === "patch") {
36
- patches.push(part)
35
+ if (part.type === 'patch') {
36
+ patches.push(part);
37
37
  }
38
- continue
38
+ continue;
39
39
  }
40
40
 
41
41
  if (!revert) {
42
- if ((msg.info.id === input.messageID && !input.partID) || part.id === input.partID) {
42
+ if (
43
+ (msg.info.id === input.messageID && !input.partID) ||
44
+ part.id === input.partID
45
+ ) {
43
46
  // if no useful parts left in message, same as reverting whole message
44
- const partID = remaining.some((item) => ["text", "tool"].includes(item.type)) ? input.partID : undefined
47
+ const partID = remaining.some((item) =>
48
+ ['text', 'tool'].includes(item.type)
49
+ )
50
+ ? input.partID
51
+ : undefined;
45
52
  revert = {
46
53
  messageID: !partID && lastUser ? lastUser.id : msg.info.id,
47
54
  partID,
48
- }
55
+ };
49
56
  }
50
- remaining.push(part)
57
+ remaining.push(part);
51
58
  }
52
59
  }
53
60
  }
54
61
 
55
62
  if (revert) {
56
- const session = await Session.get(input.sessionID)
57
- revert.snapshot = session.revert?.snapshot ?? (await Snapshot.track())
58
- await Snapshot.revert(patches)
59
- if (revert.snapshot) revert.diff = await Snapshot.diff(revert.snapshot)
63
+ const session = await Session.get(input.sessionID);
64
+ revert.snapshot = session.revert?.snapshot ?? (await Snapshot.track());
65
+ await Snapshot.revert(patches);
66
+ if (revert.snapshot) revert.diff = await Snapshot.diff(revert.snapshot);
60
67
  return Session.update(input.sessionID, (draft) => {
61
- draft.revert = revert
62
- })
68
+ draft.revert = revert;
69
+ });
63
70
  }
64
- return session
71
+ return session;
65
72
  }
66
73
 
67
74
  export async function unrevert(input: { sessionID: string }) {
68
- log.info("unreverting", input)
69
- SessionPrompt.assertNotBusy(input.sessionID)
70
- const session = await Session.get(input.sessionID)
71
- if (!session.revert) return session
72
- if (session.revert.snapshot) await Snapshot.restore(session.revert.snapshot)
75
+ log.info('unreverting', input);
76
+ SessionPrompt.assertNotBusy(input.sessionID);
77
+ const session = await Session.get(input.sessionID);
78
+ if (!session.revert) return session;
79
+ if (session.revert.snapshot)
80
+ await Snapshot.restore(session.revert.snapshot);
73
81
  const next = await Session.update(input.sessionID, (draft) => {
74
- draft.revert = undefined
75
- })
76
- return next
82
+ draft.revert = undefined;
83
+ });
84
+ return next;
77
85
  }
78
86
 
79
87
  export async function cleanup(session: Session.Info) {
80
- if (!session.revert) return
81
- const sessionID = session.id
82
- let msgs = await Session.messages({ sessionID })
83
- const messageID = session.revert.messageID
84
- const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID)
85
- msgs = preserve
88
+ if (!session.revert) return;
89
+ const sessionID = session.id;
90
+ let msgs = await Session.messages({ sessionID });
91
+ const messageID = session.revert.messageID;
92
+ const [preserve, remove] = splitWhen(msgs, (x) => x.info.id === messageID);
93
+ msgs = preserve;
86
94
  for (const msg of remove) {
87
- await Storage.remove(["message", sessionID, msg.info.id])
88
- await Bus.publish(MessageV2.Event.Removed, { sessionID: sessionID, messageID: msg.info.id })
95
+ await Storage.remove(['message', sessionID, msg.info.id]);
96
+ await Bus.publish(MessageV2.Event.Removed, {
97
+ sessionID: sessionID,
98
+ messageID: msg.info.id,
99
+ });
89
100
  }
90
- const last = preserve.at(-1)
101
+ const last = preserve.at(-1);
91
102
  if (session.revert.partID && last) {
92
- const partID = session.revert.partID
93
- const [preserveParts, removeParts] = splitWhen(last.parts, (x) => x.id === partID)
94
- last.parts = preserveParts
103
+ const partID = session.revert.partID;
104
+ const [preserveParts, removeParts] = splitWhen(
105
+ last.parts,
106
+ (x) => x.id === partID
107
+ );
108
+ last.parts = preserveParts;
95
109
  for (const part of removeParts) {
96
- await Storage.remove(["part", last.info.id, part.id])
110
+ await Storage.remove(['part', last.info.id, part.id]);
97
111
  await Bus.publish(MessageV2.Event.PartRemoved, {
98
112
  sessionID: sessionID,
99
113
  messageID: last.info.id,
100
114
  partID: part.id,
101
- })
115
+ });
102
116
  }
103
117
  }
104
118
  await Session.update(sessionID, (draft) => {
105
- draft.revert = undefined
106
- })
119
+ draft.revert = undefined;
120
+ });
107
121
  }
108
122
  }
@@ -1,75 +1,75 @@
1
- import { Bus } from "../bus"
2
- import { Instance } from "../project/instance"
3
- import z from "zod"
1
+ import { Bus } from '../bus';
2
+ import { Instance } from '../project/instance';
3
+ import z from 'zod';
4
4
 
5
5
  export namespace SessionStatus {
6
6
  export const Info = z
7
7
  .union([
8
8
  z.object({
9
- type: z.literal("idle"),
9
+ type: z.literal('idle'),
10
10
  }),
11
11
  z.object({
12
- type: z.literal("retry"),
12
+ type: z.literal('retry'),
13
13
  attempt: z.number(),
14
14
  message: z.string(),
15
15
  next: z.number(),
16
16
  }),
17
17
  z.object({
18
- type: z.literal("busy"),
18
+ type: z.literal('busy'),
19
19
  }),
20
20
  ])
21
21
  .meta({
22
- ref: "SessionStatus",
23
- })
24
- export type Info = z.infer<typeof Info>
22
+ ref: 'SessionStatus',
23
+ });
24
+ export type Info = z.infer<typeof Info>;
25
25
 
26
26
  export const Event = {
27
27
  Status: Bus.event(
28
- "session.status",
28
+ 'session.status',
29
29
  z.object({
30
30
  sessionID: z.string(),
31
31
  status: Info,
32
- }),
32
+ })
33
33
  ),
34
34
  // deprecated
35
35
  Idle: Bus.event(
36
- "session.idle",
36
+ 'session.idle',
37
37
  z.object({
38
38
  sessionID: z.string(),
39
- }),
39
+ })
40
40
  ),
41
- }
41
+ };
42
42
 
43
43
  const state = Instance.state(() => {
44
- const data: Record<string, Info> = {}
45
- return data
46
- })
44
+ const data: Record<string, Info> = {};
45
+ return data;
46
+ });
47
47
 
48
48
  export function get(sessionID: string) {
49
49
  return (
50
50
  state()[sessionID] ?? {
51
- type: "idle",
51
+ type: 'idle',
52
52
  }
53
- )
53
+ );
54
54
  }
55
55
 
56
56
  export function list() {
57
- return Object.values(state())
57
+ return Object.values(state());
58
58
  }
59
59
 
60
60
  export function set(sessionID: string, status: Info) {
61
61
  Bus.publish(Event.Status, {
62
62
  sessionID,
63
63
  status,
64
- })
65
- if (status.type === "idle") {
64
+ });
65
+ if (status.type === 'idle') {
66
66
  // deprecated
67
67
  Bus.publish(Event.Idle, {
68
68
  sessionID,
69
- })
70
- delete state()[sessionID]
71
- return
69
+ });
70
+ delete state()[sessionID];
71
+ return;
72
72
  }
73
- state()[sessionID] = status
73
+ state()[sessionID] = status;
74
74
  }
75
75
  }
@@ -1,21 +1,21 @@
1
- import { Provider } from "../provider/provider"
2
- import { fn } from "../util/fn"
3
- import z from "zod"
4
- import { Session } from "."
5
- import { generateText, type ModelMessage } from "ai"
6
- import { MessageV2 } from "./message-v2"
7
- import { Identifier } from "../id/id"
8
- import { Snapshot } from "../snapshot"
9
- import { ProviderTransform } from "../provider/transform"
10
- import { SystemPrompt } from "./system"
11
- import { Log } from "../util/log"
12
- import path from "path"
13
- import { Instance } from "../project/instance"
14
- import { Storage } from "../storage/storage"
15
- import { Bus } from "../bus"
1
+ import { Provider } from '../provider/provider';
2
+ import { fn } from '../util/fn';
3
+ import z from 'zod';
4
+ import { Session } from '.';
5
+ import { generateText, type ModelMessage } from 'ai';
6
+ import { MessageV2 } from './message-v2';
7
+ import { Identifier } from '../id/id';
8
+ import { Snapshot } from '../snapshot';
9
+ import { ProviderTransform } from '../provider/transform';
10
+ import { SystemPrompt } from './system';
11
+ import { Log } from '../util/log';
12
+ import path from 'path';
13
+ import { Instance } from '../project/instance';
14
+ import { Storage } from '../storage/storage';
15
+ import { Bus } from '../bus';
16
16
 
17
17
  export namespace SessionSummary {
18
- const log = Log.create({ service: "session.summary" })
18
+ const log = Log.create({ service: 'session.summary' });
19
19
 
20
20
  export const summarize = fn(
21
21
  z.object({
@@ -23,104 +23,122 @@ export namespace SessionSummary {
23
23
  messageID: z.string(),
24
24
  }),
25
25
  async (input) => {
26
- const all = await Session.messages({ sessionID: input.sessionID })
26
+ const all = await Session.messages({ sessionID: input.sessionID });
27
27
  await Promise.all([
28
28
  summarizeSession({ sessionID: input.sessionID, messages: all }),
29
29
  summarizeMessage({ messageID: input.messageID, messages: all }),
30
- ])
31
- },
32
- )
30
+ ]);
31
+ }
32
+ );
33
33
 
34
- async function summarizeSession(input: { sessionID: string; messages: MessageV2.WithParts[] }) {
34
+ async function summarizeSession(input: {
35
+ sessionID: string;
36
+ messages: MessageV2.WithParts[];
37
+ }) {
35
38
  const files = new Set(
36
39
  input.messages
37
40
  .flatMap((x) => x.parts)
38
- .filter((x) => x.type === "patch")
41
+ .filter((x) => x.type === 'patch')
39
42
  .flatMap((x) => x.files)
40
- .map((x) => path.relative(Instance.worktree, x)),
41
- )
43
+ .map((x) => path.relative(Instance.worktree, x))
44
+ );
42
45
  const diffs = await computeDiff({ messages: input.messages }).then((x) =>
43
46
  x.filter((x) => {
44
- return files.has(x.file)
45
- }),
46
- )
47
+ return files.has(x.file);
48
+ })
49
+ );
47
50
  await Session.update(input.sessionID, (draft) => {
48
51
  draft.summary = {
49
52
  additions: diffs.reduce((sum, x) => sum + x.additions, 0),
50
53
  deletions: diffs.reduce((sum, x) => sum + x.deletions, 0),
51
54
  files: diffs.length,
52
- }
53
- })
54
- await Storage.write(["session_diff", input.sessionID], diffs)
55
+ };
56
+ });
57
+ await Storage.write(['session_diff', input.sessionID], diffs);
55
58
  Bus.publish(Session.Event.Diff, {
56
59
  sessionID: input.sessionID,
57
60
  diff: diffs,
58
- })
61
+ });
59
62
  }
60
63
 
61
- async function summarizeMessage(input: { messageID: string; messages: MessageV2.WithParts[] }) {
64
+ async function summarizeMessage(input: {
65
+ messageID: string;
66
+ messages: MessageV2.WithParts[];
67
+ }) {
62
68
  const messages = input.messages.filter(
63
- (m) => m.info.id === input.messageID || (m.info.role === "assistant" && m.info.parentID === input.messageID),
64
- )
65
- const msgWithParts = messages.find((m) => m.info.id === input.messageID)!
66
- const userMsg = msgWithParts.info as MessageV2.User
67
- const diffs = await computeDiff({ messages })
69
+ (m) =>
70
+ m.info.id === input.messageID ||
71
+ (m.info.role === 'assistant' && m.info.parentID === input.messageID)
72
+ );
73
+ const msgWithParts = messages.find((m) => m.info.id === input.messageID)!;
74
+ const userMsg = msgWithParts.info as MessageV2.User;
75
+ const diffs = await computeDiff({ messages });
68
76
  userMsg.summary = {
69
77
  ...userMsg.summary,
70
78
  diffs,
71
- }
72
- await Session.updateMessage(userMsg)
79
+ };
80
+ await Session.updateMessage(userMsg);
73
81
 
74
- const assistantMsg = messages.find((m) => m.info.role === "assistant")!.info as MessageV2.Assistant
75
- const small = await Provider.getSmallModel(assistantMsg.providerID)
76
- if (!small) return
82
+ const assistantMsg = messages.find((m) => m.info.role === 'assistant')!
83
+ .info as MessageV2.Assistant;
84
+ const small = await Provider.getSmallModel(assistantMsg.providerID);
85
+ if (!small) return;
77
86
 
78
- const textPart = msgWithParts.parts.find((p) => p.type === "text" && !p.synthetic) as MessageV2.TextPart
87
+ const textPart = msgWithParts.parts.find(
88
+ (p) => p.type === 'text' && !p.synthetic
89
+ ) as MessageV2.TextPart;
79
90
  if (textPart && !userMsg.summary?.title) {
80
91
  const result = await generateText({
81
92
  maxOutputTokens: small.info.reasoning ? 1500 : 20,
82
- providerOptions: ProviderTransform.providerOptions(small.npm, small.providerID, {}),
93
+ providerOptions: ProviderTransform.providerOptions(
94
+ small.npm,
95
+ small.providerID,
96
+ {}
97
+ ),
83
98
  messages: [
84
99
  ...SystemPrompt.title(small.providerID).map(
85
100
  (x): ModelMessage => ({
86
- role: "system",
101
+ role: 'system',
87
102
  content: x,
88
- }),
103
+ })
89
104
  ),
90
105
  {
91
- role: "user" as const,
106
+ role: 'user' as const,
92
107
  content: `
93
108
  The following is the text to summarize:
94
109
  <text>
95
- ${textPart?.text ?? ""}
110
+ ${textPart?.text ?? ''}
96
111
  </text>
97
112
  `,
98
113
  },
99
114
  ],
100
115
  headers: small.info.headers,
101
116
  model: small.language,
102
- })
103
- log.info("title", { title: result.text })
104
- userMsg.summary.title = result.text
105
- await Session.updateMessage(userMsg)
117
+ });
118
+ log.info('title', { title: result.text });
119
+ userMsg.summary.title = result.text;
120
+ await Session.updateMessage(userMsg);
106
121
  }
107
122
 
108
123
  if (
109
124
  messages.some(
110
125
  (m) =>
111
- m.info.role === "assistant" && m.parts.some((p) => p.type === "step-finish" && p.reason !== "tool-calls"),
126
+ m.info.role === 'assistant' &&
127
+ m.parts.some(
128
+ (p) => p.type === 'step-finish' && p.reason !== 'tool-calls'
129
+ )
112
130
  )
113
131
  ) {
114
132
  let summary = messages
115
- .findLast((m) => m.info.role === "assistant")
116
- ?.parts.findLast((p) => p.type === "text")?.text
133
+ .findLast((m) => m.info.role === 'assistant')
134
+ ?.parts.findLast((p) => p.type === 'text')?.text;
117
135
  if (!summary || diffs.length > 0) {
118
136
  const result = await generateText({
119
137
  model: small.language,
120
138
  maxOutputTokens: 100,
121
139
  messages: [
122
140
  {
123
- role: "user",
141
+ role: 'user',
124
142
  content: `
125
143
  Summarize the following conversation into 2 sentences MAX explaining what the assistant did and why. Do not explain the user's input. Do not speak in the third person about the assistant.
126
144
  <conversation>
@@ -130,50 +148,53 @@ export namespace SessionSummary {
130
148
  },
131
149
  ],
132
150
  headers: small.info.headers,
133
- }).catch(() => {})
134
- if (result) summary = result.text
151
+ }).catch(() => {});
152
+ if (result) summary = result.text;
135
153
  }
136
- userMsg.summary.body = summary
137
- log.info("body", { body: summary })
138
- await Session.updateMessage(userMsg)
154
+ userMsg.summary.body = summary;
155
+ log.info('body', { body: summary });
156
+ await Session.updateMessage(userMsg);
139
157
  }
140
158
  }
141
159
 
142
160
  export const diff = fn(
143
161
  z.object({
144
- sessionID: Identifier.schema("session"),
145
- messageID: Identifier.schema("message").optional(),
162
+ sessionID: Identifier.schema('session'),
163
+ messageID: Identifier.schema('message').optional(),
146
164
  }),
147
165
  async (input) => {
148
- return Storage.read<Snapshot.FileDiff[]>(["session_diff", input.sessionID]).catch(() => [])
149
- },
150
- )
166
+ return Storage.read<Snapshot.FileDiff[]>([
167
+ 'session_diff',
168
+ input.sessionID,
169
+ ]).catch(() => []);
170
+ }
171
+ );
151
172
 
152
173
  async function computeDiff(input: { messages: MessageV2.WithParts[] }) {
153
- let from: string | undefined
154
- let to: string | undefined
174
+ let from: string | undefined;
175
+ let to: string | undefined;
155
176
 
156
177
  // scan assistant messages to find earliest from and latest to
157
178
  // snapshot
158
179
  for (const item of input.messages) {
159
180
  if (!from) {
160
181
  for (const part of item.parts) {
161
- if (part.type === "step-start" && part.snapshot) {
162
- from = part.snapshot
163
- break
182
+ if (part.type === 'step-start' && part.snapshot) {
183
+ from = part.snapshot;
184
+ break;
164
185
  }
165
186
  }
166
187
  }
167
188
 
168
189
  for (const part of item.parts) {
169
- if (part.type === "step-finish" && part.snapshot) {
170
- to = part.snapshot
171
- break
190
+ if (part.type === 'step-finish' && part.snapshot) {
191
+ to = part.snapshot;
192
+ break;
172
193
  }
173
194
  }
174
195
  }
175
196
 
176
- if (from && to) return Snapshot.diffFull(from, to)
177
- return []
197
+ if (from && to) return Snapshot.diffFull(from, to);
198
+ return [];
178
199
  }
179
200
  }