@agi-cli/server 0.1.120 → 0.1.121

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 (70) hide show
  1. package/package.json +3 -3
  2. package/src/index.ts +5 -5
  3. package/src/openapi/paths/git.ts +4 -0
  4. package/src/routes/ask.ts +13 -14
  5. package/src/routes/branch.ts +2 -2
  6. package/src/routes/config/agents.ts +1 -1
  7. package/src/routes/config/cwd.ts +1 -1
  8. package/src/routes/config/main.ts +1 -1
  9. package/src/routes/config/models.ts +32 -4
  10. package/src/routes/config/providers.ts +1 -1
  11. package/src/routes/config/utils.ts +14 -1
  12. package/src/routes/files.ts +1 -1
  13. package/src/routes/git/commit.ts +23 -6
  14. package/src/routes/git/schemas.ts +1 -0
  15. package/src/routes/session-files.ts +1 -1
  16. package/src/routes/session-messages.ts +2 -2
  17. package/src/routes/sessions.ts +8 -6
  18. package/src/runtime/agent/registry.ts +333 -0
  19. package/src/runtime/agent/runner-reasoning.ts +108 -0
  20. package/src/runtime/agent/runner-setup.ts +265 -0
  21. package/src/runtime/agent/runner.ts +356 -0
  22. package/src/runtime/agent-registry.ts +6 -333
  23. package/src/runtime/{ask-service.ts → ask/service.ts} +5 -5
  24. package/src/runtime/{debug.ts → debug/index.ts} +1 -1
  25. package/src/runtime/{api-error.ts → errors/api-error.ts} +2 -2
  26. package/src/runtime/message/compaction-auto.ts +137 -0
  27. package/src/runtime/message/compaction-context.ts +64 -0
  28. package/src/runtime/message/compaction-detect.ts +19 -0
  29. package/src/runtime/message/compaction-limits.ts +58 -0
  30. package/src/runtime/message/compaction-mark.ts +115 -0
  31. package/src/runtime/message/compaction-prune.ts +75 -0
  32. package/src/runtime/message/compaction.ts +23 -0
  33. package/src/runtime/{history-builder.ts → message/history-builder.ts} +2 -2
  34. package/src/runtime/{message-service.ts → message/service.ts} +8 -14
  35. package/src/runtime/{history → message}/tool-history-tracker.ts +1 -1
  36. package/src/runtime/{prompt.ts → prompt/builder.ts} +1 -1
  37. package/src/runtime/{provider.ts → provider/anthropic.ts} +4 -219
  38. package/src/runtime/provider/google.ts +12 -0
  39. package/src/runtime/provider/index.ts +44 -0
  40. package/src/runtime/provider/openai.ts +26 -0
  41. package/src/runtime/provider/opencode.ts +61 -0
  42. package/src/runtime/provider/openrouter.ts +11 -0
  43. package/src/runtime/provider/solforge.ts +22 -0
  44. package/src/runtime/provider/zai.ts +53 -0
  45. package/src/runtime/{branch.ts → session/branch.ts} +1 -1
  46. package/src/runtime/{db-operations.ts → session/db-operations.ts} +1 -1
  47. package/src/runtime/{session-manager.ts → session/manager.ts} +1 -1
  48. package/src/runtime/{session-queue.ts → session/queue.ts} +2 -2
  49. package/src/runtime/stream/abort-handler.ts +65 -0
  50. package/src/runtime/stream/error-handler.ts +200 -0
  51. package/src/runtime/stream/finish-handler.ts +123 -0
  52. package/src/runtime/stream/handlers.ts +5 -0
  53. package/src/runtime/stream/step-finish.ts +93 -0
  54. package/src/runtime/stream/types.ts +17 -0
  55. package/src/runtime/{tool-context.ts → tools/context.ts} +1 -1
  56. package/src/runtime/{tool-context-setup.ts → tools/setup.ts} +3 -3
  57. package/src/runtime/{token-utils.ts → utils/token.ts} +2 -2
  58. package/src/tools/adapter.ts +4 -4
  59. package/src/runtime/compaction.ts +0 -536
  60. package/src/runtime/runner.ts +0 -654
  61. package/src/runtime/stream-handlers.ts +0 -508
  62. /package/src/runtime/{cache-optimizer.ts → context/cache-optimizer.ts} +0 -0
  63. /package/src/runtime/{environment.ts → context/environment.ts} +0 -0
  64. /package/src/runtime/{context-optimizer.ts → context/optimizer.ts} +0 -0
  65. /package/src/runtime/{debug-state.ts → debug/state.ts} +0 -0
  66. /package/src/runtime/{error-handling.ts → errors/handling.ts} +0 -0
  67. /package/src/runtime/{history-truncator.ts → message/history-truncator.ts} +0 -0
  68. /package/src/runtime/{provider-selection.ts → provider/selection.ts} +0 -0
  69. /package/src/runtime/{tool-mapping.ts → tools/mapping.ts} +0 -0
  70. /package/src/runtime/{cwd.ts → utils/cwd.ts} +0 -0
@@ -0,0 +1,200 @@
1
+ import type { getDb } from '@agi-cli/database';
2
+ import { messages, messageParts } from '@agi-cli/database/schema';
3
+ import { eq } from 'drizzle-orm';
4
+ import { APICallError } from 'ai';
5
+ import { publish } from '../../events/bus.ts';
6
+ import { toErrorPayload } from '../errors/handling.ts';
7
+ import type { RunOpts } from '../session/queue.ts';
8
+ import type { ToolAdapterContext } from '../../tools/adapter.ts';
9
+ import { pruneSession, performAutoCompaction } from '../message/compaction.ts';
10
+ import { debugLog } from '../debug/index.ts';
11
+ import { enqueueAssistantRun } from '../session/queue.ts';
12
+
13
+ export function createErrorHandler(
14
+ opts: RunOpts,
15
+ db: Awaited<ReturnType<typeof getDb>>,
16
+ getStepIndex: () => number,
17
+ sharedCtx: ToolAdapterContext,
18
+ retryCallback?: (sessionId: string) => Promise<void>,
19
+ ) {
20
+ return async (err: unknown) => {
21
+ const errorPayload = toErrorPayload(err);
22
+ const isApiError = APICallError.isInstance(err);
23
+ const stepIndex = getStepIndex();
24
+
25
+ const errObj = err as Record<string, unknown>;
26
+ const nestedError = (errObj?.error as Record<string, unknown>)?.error as
27
+ | Record<string, unknown>
28
+ | undefined;
29
+ const errorCode =
30
+ (errObj?.code as string) ?? (nestedError?.code as string) ?? '';
31
+ const errorType =
32
+ (errObj?.apiErrorType as string) ?? (nestedError?.type as string) ?? '';
33
+ const fullErrorStr = JSON.stringify(err).toLowerCase();
34
+
35
+ const isPromptTooLong =
36
+ fullErrorStr.includes('prompt is too long') ||
37
+ fullErrorStr.includes('maximum context length') ||
38
+ fullErrorStr.includes('too many tokens') ||
39
+ fullErrorStr.includes('context_length_exceeded') ||
40
+ fullErrorStr.includes('request too large') ||
41
+ fullErrorStr.includes('exceeds the model') ||
42
+ fullErrorStr.includes('context window') ||
43
+ fullErrorStr.includes('input is too long') ||
44
+ errorCode === 'context_length_exceeded' ||
45
+ errorType === 'invalid_request_error';
46
+
47
+ debugLog(
48
+ `[stream-handlers] isPromptTooLong: ${isPromptTooLong}, errorCode: ${errorCode}, errorType: ${errorType}`,
49
+ );
50
+
51
+ if (isPromptTooLong && !opts.isCompactCommand) {
52
+ debugLog(
53
+ '[stream-handlers] Prompt too long detected, auto-compacting...',
54
+ );
55
+ let compactionSucceeded = false;
56
+ try {
57
+ const publishWrapper = (event: {
58
+ type: string;
59
+ sessionId: string;
60
+ payload: Record<string, unknown>;
61
+ }) => {
62
+ publish(event as Parameters<typeof publish>[0]);
63
+ };
64
+ const compactResult = await performAutoCompaction(
65
+ db,
66
+ opts.sessionId,
67
+ opts.assistantMessageId,
68
+ publishWrapper,
69
+ opts.provider,
70
+ opts.model,
71
+ );
72
+ if (compactResult.success) {
73
+ debugLog(
74
+ `[stream-handlers] Auto-compaction succeeded: ${compactResult.summary?.slice(0, 100)}...`,
75
+ );
76
+ compactionSucceeded = true;
77
+ } else {
78
+ debugLog(
79
+ `[stream-handlers] Auto-compaction failed: ${compactResult.error}, falling back to prune`,
80
+ );
81
+ const pruneResult = await pruneSession(db, opts.sessionId);
82
+ debugLog(
83
+ `[stream-handlers] Fallback pruned ${pruneResult.pruned} parts, saved ~${pruneResult.saved} tokens`,
84
+ );
85
+ compactionSucceeded = pruneResult.pruned > 0;
86
+ }
87
+ } catch (compactErr) {
88
+ debugLog(
89
+ `[stream-handlers] Auto-compact error: ${compactErr instanceof Error ? compactErr.message : String(compactErr)}`,
90
+ );
91
+ }
92
+
93
+ if (compactionSucceeded) {
94
+ await db
95
+ .update(messages)
96
+ .set({
97
+ status: 'completed',
98
+ })
99
+ .where(eq(messages.id, opts.assistantMessageId));
100
+
101
+ publish({
102
+ type: 'message.completed',
103
+ sessionId: opts.sessionId,
104
+ payload: {
105
+ id: opts.assistantMessageId,
106
+ autoCompacted: true,
107
+ },
108
+ });
109
+
110
+ if (retryCallback) {
111
+ debugLog('[stream-handlers] Triggering retry after compaction...');
112
+ const newAssistantMessageId = crypto.randomUUID();
113
+ await db.insert(messages).values({
114
+ id: newAssistantMessageId,
115
+ sessionId: opts.sessionId,
116
+ role: 'assistant',
117
+ status: 'pending',
118
+ agent: opts.agent,
119
+ provider: opts.provider,
120
+ model: opts.model,
121
+ createdAt: Date.now(),
122
+ });
123
+
124
+ publish({
125
+ type: 'message.created',
126
+ sessionId: opts.sessionId,
127
+ payload: { id: newAssistantMessageId, role: 'assistant' },
128
+ });
129
+
130
+ enqueueAssistantRun(
131
+ {
132
+ ...opts,
133
+ assistantMessageId: newAssistantMessageId,
134
+ },
135
+ retryCallback,
136
+ );
137
+ } else {
138
+ debugLog(
139
+ '[stream-handlers] No retryCallback provided, cannot auto-retry',
140
+ );
141
+ }
142
+
143
+ return;
144
+ }
145
+ }
146
+
147
+ const errorPartId = crypto.randomUUID();
148
+ const displayMessage =
149
+ isPromptTooLong && !opts.isCompactCommand
150
+ ? `${errorPayload.message}. Context auto-compacted - please retry your message.`
151
+ : errorPayload.message;
152
+ await db.insert(messageParts).values({
153
+ id: errorPartId,
154
+ messageId: opts.assistantMessageId,
155
+ index: await sharedCtx.nextIndex(),
156
+ stepIndex,
157
+ type: 'error',
158
+ content: JSON.stringify({
159
+ message: displayMessage,
160
+ type: errorPayload.type,
161
+ details: errorPayload.details,
162
+ isAborted: false,
163
+ }),
164
+ agent: opts.agent,
165
+ provider: opts.provider,
166
+ model: opts.model,
167
+ startedAt: Date.now(),
168
+ completedAt: Date.now(),
169
+ });
170
+
171
+ await db
172
+ .update(messages)
173
+ .set({
174
+ status: 'error',
175
+ error: displayMessage,
176
+ errorType: errorPayload.type,
177
+ errorDetails: JSON.stringify({
178
+ ...errorPayload.details,
179
+ isApiError,
180
+ autoCompacted: isPromptTooLong && !opts.isCompactCommand,
181
+ }),
182
+ isAborted: false,
183
+ })
184
+ .where(eq(messages.id, opts.assistantMessageId));
185
+
186
+ publish({
187
+ type: 'error',
188
+ sessionId: opts.sessionId,
189
+ payload: {
190
+ messageId: opts.assistantMessageId,
191
+ partId: errorPartId,
192
+ error: displayMessage,
193
+ errorType: errorPayload.type,
194
+ details: errorPayload.details,
195
+ isAborted: false,
196
+ autoCompacted: isPromptTooLong && !opts.isCompactCommand,
197
+ },
198
+ });
199
+ };
200
+ }
@@ -0,0 +1,123 @@
1
+ import type { getDb } from '@agi-cli/database';
2
+ import { messages, messageParts } from '@agi-cli/database/schema';
3
+ import { eq } from 'drizzle-orm';
4
+ import { publish } from '../../events/bus.ts';
5
+ import { estimateModelCostUsd } from '@agi-cli/sdk';
6
+ import type { RunOpts } from '../session/queue.ts';
7
+ import {
8
+ pruneSession,
9
+ isOverflow,
10
+ getModelLimits,
11
+ type TokenUsage,
12
+ markSessionCompacted,
13
+ } from '../message/compaction.ts';
14
+ import { debugLog } from '../debug/index.ts';
15
+ import type { FinishEvent } from './types.ts';
16
+
17
+ export function createFinishHandler(
18
+ opts: RunOpts,
19
+ db: Awaited<ReturnType<typeof getDb>>,
20
+ completeAssistantMessageFn: (
21
+ fin: FinishEvent,
22
+ opts: RunOpts,
23
+ db: Awaited<ReturnType<typeof getDb>>,
24
+ ) => Promise<void>,
25
+ ) {
26
+ return async (fin: FinishEvent) => {
27
+ try {
28
+ await completeAssistantMessageFn(fin, opts, db);
29
+ } catch {}
30
+
31
+ if (opts.isCompactCommand && fin.finishReason !== 'error') {
32
+ const assistantParts = await db
33
+ .select()
34
+ .from(messageParts)
35
+ .where(eq(messageParts.messageId, opts.assistantMessageId));
36
+ const hasTextContent = assistantParts.some(
37
+ (p) => p.type === 'text' && p.content && p.content !== '{"text":""}',
38
+ );
39
+
40
+ if (!hasTextContent) {
41
+ debugLog(
42
+ '[stream-handlers] /compact finished but no summary generated, skipping compaction marking',
43
+ );
44
+ } else {
45
+ try {
46
+ debugLog(
47
+ `[stream-handlers] /compact complete, marking session compacted`,
48
+ );
49
+ const result = await markSessionCompacted(
50
+ db,
51
+ opts.sessionId,
52
+ opts.assistantMessageId,
53
+ );
54
+ debugLog(
55
+ `[stream-handlers] Compacted ${result.compacted} parts, saved ~${result.saved} tokens`,
56
+ );
57
+ } catch (err) {
58
+ debugLog(
59
+ `[stream-handlers] Compaction failed: ${err instanceof Error ? err.message : String(err)}`,
60
+ );
61
+ }
62
+ }
63
+ }
64
+
65
+ const sessRows = await db
66
+ .select()
67
+ .from(messages)
68
+ .where(eq(messages.id, opts.assistantMessageId));
69
+
70
+ const usage = sessRows[0]
71
+ ? {
72
+ inputTokens: Number(sessRows[0].promptTokens ?? 0),
73
+ outputTokens: Number(sessRows[0].completionTokens ?? 0),
74
+ totalTokens: Number(sessRows[0].totalTokens ?? 0),
75
+ cachedInputTokens: Number(sessRows[0].cachedInputTokens ?? 0),
76
+ }
77
+ : fin.usage;
78
+
79
+ const costUsd = usage
80
+ ? estimateModelCostUsd(opts.provider, opts.model, usage)
81
+ : undefined;
82
+
83
+ if (usage) {
84
+ try {
85
+ const limits = getModelLimits(opts.provider, opts.model);
86
+ if (limits) {
87
+ const tokenUsage: TokenUsage = {
88
+ input: usage.inputTokens ?? 0,
89
+ output: usage.outputTokens ?? 0,
90
+ cacheRead:
91
+ (usage as { cachedInputTokens?: number }).cachedInputTokens ?? 0,
92
+ };
93
+
94
+ if (isOverflow(tokenUsage, limits)) {
95
+ debugLog(
96
+ `[stream-handlers] Context overflow detected, triggering prune for session ${opts.sessionId}`,
97
+ );
98
+ pruneSession(db, opts.sessionId).catch((err) => {
99
+ debugLog(
100
+ `[stream-handlers] Prune failed: ${err instanceof Error ? err.message : String(err)}`,
101
+ );
102
+ });
103
+ }
104
+ }
105
+ } catch (err) {
106
+ debugLog(
107
+ `[stream-handlers] Overflow check failed: ${err instanceof Error ? err.message : String(err)}`,
108
+ );
109
+ }
110
+ }
111
+
112
+ publish({
113
+ type: 'message.completed',
114
+ sessionId: opts.sessionId,
115
+ payload: {
116
+ id: opts.assistantMessageId,
117
+ usage,
118
+ costUsd,
119
+ finishReason: fin.finishReason,
120
+ },
121
+ });
122
+ };
123
+ }
@@ -0,0 +1,5 @@
1
+ export { createStepFinishHandler } from './step-finish.ts';
2
+ export { createErrorHandler } from './error-handler.ts';
3
+ export { createAbortHandler } from './abort-handler.ts';
4
+ export { createFinishHandler } from './finish-handler.ts';
5
+ export type { StepFinishEvent, FinishEvent, AbortEvent } from './types.ts';
@@ -0,0 +1,93 @@
1
+ import type { getDb } from '@agi-cli/database';
2
+ import { messageParts } from '@agi-cli/database/schema';
3
+ import { eq } from 'drizzle-orm';
4
+ import { publish } from '../../events/bus.ts';
5
+ import type { RunOpts } from '../session/queue.ts';
6
+ import type { ToolAdapterContext } from '../../tools/adapter.ts';
7
+ import type { UsageData, ProviderMetadata } from '../session/db-operations.ts';
8
+ import type { StepFinishEvent } from './types.ts';
9
+
10
+ export function createStepFinishHandler(
11
+ opts: RunOpts,
12
+ db: Awaited<ReturnType<typeof getDb>>,
13
+ getStepIndex: () => number,
14
+ incrementStepIndex: () => number,
15
+ getCurrentPartId: () => string | null,
16
+ updateCurrentPartId: (id: string | null) => void,
17
+ updateAccumulated: (text: string) => void,
18
+ sharedCtx: ToolAdapterContext,
19
+ updateSessionTokensIncrementalFn: (
20
+ usage: UsageData,
21
+ providerMetadata: ProviderMetadata | undefined,
22
+ opts: RunOpts,
23
+ db: Awaited<ReturnType<typeof getDb>>,
24
+ ) => Promise<void>,
25
+ updateMessageTokensIncrementalFn: (
26
+ usage: UsageData,
27
+ providerMetadata: ProviderMetadata | undefined,
28
+ opts: RunOpts,
29
+ db: Awaited<ReturnType<typeof getDb>>,
30
+ ) => Promise<void>,
31
+ ) {
32
+ return async (step: StepFinishEvent) => {
33
+ const finishedAt = Date.now();
34
+ const currentPartId = getCurrentPartId();
35
+ const stepIndex = getStepIndex();
36
+
37
+ try {
38
+ if (currentPartId) {
39
+ await db
40
+ .update(messageParts)
41
+ .set({ completedAt: finishedAt })
42
+ .where(eq(messageParts.id, currentPartId));
43
+ }
44
+ } catch {}
45
+
46
+ if (step.usage) {
47
+ try {
48
+ await updateSessionTokensIncrementalFn(
49
+ step.usage,
50
+ step.experimental_providerMetadata,
51
+ opts,
52
+ db,
53
+ );
54
+ } catch {}
55
+
56
+ try {
57
+ await updateMessageTokensIncrementalFn(
58
+ step.usage,
59
+ step.experimental_providerMetadata,
60
+ opts,
61
+ db,
62
+ );
63
+ } catch {}
64
+ }
65
+
66
+ try {
67
+ publish({
68
+ type: 'finish-step',
69
+ sessionId: opts.sessionId,
70
+ payload: {
71
+ stepIndex,
72
+ usage: step.usage,
73
+ finishReason: step.finishReason,
74
+ response: step.response,
75
+ },
76
+ });
77
+ if (step.usage) {
78
+ publish({
79
+ type: 'usage',
80
+ sessionId: opts.sessionId,
81
+ payload: { stepIndex, ...step.usage },
82
+ });
83
+ }
84
+ } catch {}
85
+
86
+ try {
87
+ const newStepIndex = incrementStepIndex();
88
+ sharedCtx.stepIndex = newStepIndex;
89
+ updateCurrentPartId(null);
90
+ updateAccumulated('');
91
+ } catch {}
92
+ };
93
+ }
@@ -0,0 +1,17 @@
1
+ import type { UsageData, ProviderMetadata } from '../session/db-operations.ts';
2
+
3
+ export type StepFinishEvent = {
4
+ usage?: UsageData;
5
+ finishReason?: string;
6
+ response?: unknown;
7
+ experimental_providerMetadata?: ProviderMetadata;
8
+ };
9
+
10
+ export type FinishEvent = {
11
+ usage?: Pick<UsageData, 'inputTokens' | 'outputTokens' | 'totalTokens'>;
12
+ finishReason?: string;
13
+ };
14
+
15
+ export type AbortEvent = {
16
+ steps: unknown[];
17
+ };
@@ -1,7 +1,7 @@
1
1
  import { eq } from 'drizzle-orm';
2
2
  import type { DB } from '@agi-cli/database';
3
3
  import { messageParts } from '@agi-cli/database/schema';
4
- import { publish } from '../events/bus.ts';
4
+ import { publish } from '../../events/bus.ts';
5
5
 
6
6
  export type StepExecutionState = {
7
7
  chain: Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import type { getDb } from '@agi-cli/database';
2
- import { time } from './debug.ts';
3
- import type { ToolAdapterContext } from '../tools/adapter.ts';
4
- import type { RunOpts } from './session-queue.ts';
2
+ import { time } from '../debug/index.ts';
3
+ import type { ToolAdapterContext } from '../../tools/adapter.ts';
4
+ import type { RunOpts } from '../session/queue.ts';
5
5
 
6
6
  export type RunnerToolContext = ToolAdapterContext & { stepIndex: number };
7
7
 
@@ -1,6 +1,6 @@
1
1
  import { catalog } from '@agi-cli/sdk';
2
- import { debugLog } from './debug.ts';
3
- import type { ProviderName } from './provider.ts';
2
+ import { debugLog } from '../debug/index.ts';
3
+ import type { ProviderName } from '../provider/index.ts';
4
4
 
5
5
  /**
6
6
  * Gets the maximum output tokens allowed for a given provider/model combination.
@@ -3,18 +3,18 @@ import { messageParts, sessions } from '@agi-cli/database/schema';
3
3
  import { eq } from 'drizzle-orm';
4
4
  import { publish } from '../events/bus.ts';
5
5
  import type { DiscoveredTool } from '@agi-cli/sdk';
6
- import { getCwd, setCwd, joinRelative } from '../runtime/cwd.ts';
6
+ import { getCwd, setCwd, joinRelative } from '../runtime/utils/cwd.ts';
7
7
  import type {
8
8
  ToolAdapterContext,
9
9
  StepExecutionState,
10
- } from '../runtime/tool-context.ts';
10
+ } from '../runtime/tools/context.ts';
11
11
  import { isToolError } from '@agi-cli/sdk/tools/error';
12
12
  import {
13
13
  toClaudeCodeName,
14
14
  requiresClaudeCodeNaming,
15
- } from '../runtime/tool-mapping.ts';
15
+ } from '../runtime/tools/mapping.ts';
16
16
 
17
- export type { ToolAdapterContext } from '../runtime/tool-context.ts';
17
+ export type { ToolAdapterContext } from '../runtime/tools/context.ts';
18
18
 
19
19
  type ToolExecuteSignature = Tool['execute'] extends (
20
20
  input: infer Input,