@ottocode/server 0.1.215 → 0.1.217
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.
- package/package.json +3 -3
- package/src/openapi/paths/config.ts +2 -0
- package/src/openapi/paths/messages.ts +5 -0
- package/src/openapi/paths/sessions.ts +98 -3
- package/src/openapi/paths/setu.ts +39 -1
- package/src/openapi/paths/skills.ts +84 -0
- package/src/openapi/schemas.ts +1 -0
- package/src/routes/config/defaults.ts +7 -0
- package/src/routes/config/main.ts +2 -0
- package/src/routes/session-messages.ts +2 -1
- package/src/routes/sessions.ts +5 -0
- package/src/routes/skills.ts +47 -0
- package/src/runtime/message/history-builder.ts +17 -18
- package/src/runtime/tools/mapping.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.217",
|
|
4
4
|
"description": "HTTP API server for ottocode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ottocode/sdk": "0.1.
|
|
53
|
-
"@ottocode/database": "0.1.
|
|
52
|
+
"@ottocode/sdk": "0.1.217",
|
|
53
|
+
"@ottocode/database": "0.1.217",
|
|
54
54
|
"drizzle-orm": "^0.44.5",
|
|
55
55
|
"hono": "^4.9.9",
|
|
56
56
|
"zod": "^4.3.6"
|
|
@@ -178,6 +178,7 @@ export const configPaths = {
|
|
|
178
178
|
agent: { type: 'string' },
|
|
179
179
|
provider: { type: 'string' },
|
|
180
180
|
model: { type: 'string' },
|
|
181
|
+
reasoningText: { type: 'boolean' },
|
|
181
182
|
scope: {
|
|
182
183
|
type: 'string',
|
|
183
184
|
enum: ['global', 'local'],
|
|
@@ -203,6 +204,7 @@ export const configPaths = {
|
|
|
203
204
|
agent: { type: 'string' },
|
|
204
205
|
provider: { type: 'string' },
|
|
205
206
|
model: { type: 'string' },
|
|
207
|
+
reasoningText: { type: 'boolean' },
|
|
206
208
|
},
|
|
207
209
|
required: ['agent', 'provider', 'model'],
|
|
208
210
|
},
|
|
@@ -67,6 +67,11 @@ export const messagesPaths = {
|
|
|
67
67
|
description:
|
|
68
68
|
'Optional user-provided context to include in the system prompt.',
|
|
69
69
|
},
|
|
70
|
+
reasoningText: {
|
|
71
|
+
type: 'boolean',
|
|
72
|
+
description:
|
|
73
|
+
'Enable extended thinking / reasoning for models that support it.',
|
|
74
|
+
},
|
|
70
75
|
},
|
|
71
76
|
},
|
|
72
77
|
},
|
|
@@ -6,15 +6,37 @@ export const sessionsPaths = {
|
|
|
6
6
|
tags: ['sessions'],
|
|
7
7
|
operationId: 'listSessions',
|
|
8
8
|
summary: 'List sessions',
|
|
9
|
-
parameters: [
|
|
9
|
+
parameters: [
|
|
10
|
+
projectQueryParam(),
|
|
11
|
+
{
|
|
12
|
+
in: 'query',
|
|
13
|
+
name: 'limit',
|
|
14
|
+
schema: { type: 'integer', default: 50, minimum: 1, maximum: 200 },
|
|
15
|
+
description: 'Maximum number of sessions to return',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
in: 'query',
|
|
19
|
+
name: 'offset',
|
|
20
|
+
schema: { type: 'integer', default: 0, minimum: 0 },
|
|
21
|
+
description: 'Offset for pagination',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
10
24
|
responses: {
|
|
11
25
|
200: {
|
|
12
26
|
description: 'OK',
|
|
13
27
|
content: {
|
|
14
28
|
'application/json': {
|
|
15
29
|
schema: {
|
|
16
|
-
type: '
|
|
17
|
-
|
|
30
|
+
type: 'object',
|
|
31
|
+
properties: {
|
|
32
|
+
items: {
|
|
33
|
+
type: 'array',
|
|
34
|
+
items: { $ref: '#/components/schemas/Session' },
|
|
35
|
+
},
|
|
36
|
+
hasMore: { type: 'boolean' },
|
|
37
|
+
nextOffset: { type: 'integer', nullable: true },
|
|
38
|
+
},
|
|
39
|
+
required: ['items', 'hasMore', 'nextOffset'],
|
|
18
40
|
},
|
|
19
41
|
},
|
|
20
42
|
},
|
|
@@ -55,6 +77,79 @@ export const sessionsPaths = {
|
|
|
55
77
|
},
|
|
56
78
|
},
|
|
57
79
|
},
|
|
80
|
+
'/v1/sessions/{sessionId}': {
|
|
81
|
+
patch: {
|
|
82
|
+
tags: ['sessions'],
|
|
83
|
+
operationId: 'updateSession',
|
|
84
|
+
summary: 'Update session preferences',
|
|
85
|
+
parameters: [
|
|
86
|
+
{
|
|
87
|
+
in: 'path',
|
|
88
|
+
name: 'sessionId',
|
|
89
|
+
required: true,
|
|
90
|
+
schema: { type: 'string' },
|
|
91
|
+
},
|
|
92
|
+
projectQueryParam(),
|
|
93
|
+
],
|
|
94
|
+
requestBody: {
|
|
95
|
+
required: true,
|
|
96
|
+
content: {
|
|
97
|
+
'application/json': {
|
|
98
|
+
schema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
title: { type: 'string' },
|
|
102
|
+
agent: { type: 'string' },
|
|
103
|
+
provider: { $ref: '#/components/schemas/Provider' },
|
|
104
|
+
model: { type: 'string' },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
responses: {
|
|
111
|
+
200: {
|
|
112
|
+
description: 'OK',
|
|
113
|
+
content: {
|
|
114
|
+
'application/json': {
|
|
115
|
+
schema: { $ref: '#/components/schemas/Session' },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
400: errorResponse(),
|
|
120
|
+
404: errorResponse(),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
delete: {
|
|
124
|
+
tags: ['sessions'],
|
|
125
|
+
operationId: 'deleteSession',
|
|
126
|
+
summary: 'Delete a session',
|
|
127
|
+
parameters: [
|
|
128
|
+
{
|
|
129
|
+
in: 'path',
|
|
130
|
+
name: 'sessionId',
|
|
131
|
+
required: true,
|
|
132
|
+
schema: { type: 'string' },
|
|
133
|
+
},
|
|
134
|
+
projectQueryParam(),
|
|
135
|
+
],
|
|
136
|
+
responses: {
|
|
137
|
+
200: {
|
|
138
|
+
description: 'OK',
|
|
139
|
+
content: {
|
|
140
|
+
'application/json': {
|
|
141
|
+
schema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: { success: { type: 'boolean' } },
|
|
144
|
+
required: ['success'],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
404: errorResponse(),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
58
153
|
'/v1/sessions/{sessionId}/abort': {
|
|
59
154
|
delete: {
|
|
60
155
|
tags: ['sessions'],
|
|
@@ -5,7 +5,7 @@ export const setuPaths = {
|
|
|
5
5
|
operationId: 'getSetuBalance',
|
|
6
6
|
summary: 'Get Setu account balance',
|
|
7
7
|
description:
|
|
8
|
-
'Returns wallet balance,
|
|
8
|
+
'Returns wallet balance, subscription, account info, limits, and usage data',
|
|
9
9
|
responses: {
|
|
10
10
|
200: {
|
|
11
11
|
description: 'OK',
|
|
@@ -19,6 +19,44 @@ export const setuPaths = {
|
|
|
19
19
|
totalSpent: { type: 'number' },
|
|
20
20
|
totalTopups: { type: 'number' },
|
|
21
21
|
requestCount: { type: 'number' },
|
|
22
|
+
scope: { type: 'string', enum: ['wallet', 'account'] },
|
|
23
|
+
payg: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
walletBalanceUsd: { type: 'number' },
|
|
27
|
+
accountBalanceUsd: { type: 'number' },
|
|
28
|
+
rawPoolUsd: { type: 'number' },
|
|
29
|
+
effectiveSpendableUsd: { type: 'number' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
limits: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
nullable: true,
|
|
35
|
+
properties: {
|
|
36
|
+
enabled: { type: 'boolean' },
|
|
37
|
+
dailyLimitUsd: { type: 'number', nullable: true },
|
|
38
|
+
dailySpentUsd: { type: 'number' },
|
|
39
|
+
dailyRemainingUsd: { type: 'number', nullable: true },
|
|
40
|
+
monthlyLimitUsd: { type: 'number', nullable: true },
|
|
41
|
+
monthlySpentUsd: { type: 'number' },
|
|
42
|
+
monthlyRemainingUsd: { type: 'number', nullable: true },
|
|
43
|
+
capRemainingUsd: { type: 'number', nullable: true },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
subscription: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
nullable: true,
|
|
49
|
+
properties: {
|
|
50
|
+
active: { type: 'boolean' },
|
|
51
|
+
tierId: { type: 'string' },
|
|
52
|
+
tierName: { type: 'string' },
|
|
53
|
+
creditsIncluded: { type: 'number' },
|
|
54
|
+
creditsUsed: { type: 'number' },
|
|
55
|
+
creditsRemaining: { type: 'number' },
|
|
56
|
+
periodStart: { type: 'string' },
|
|
57
|
+
periodEnd: { type: 'string' },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
22
60
|
},
|
|
23
61
|
required: [
|
|
24
62
|
'walletAddress',
|
|
@@ -93,6 +93,90 @@ export const skillsPaths = {
|
|
|
93
93
|
},
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
|
+
'/v1/skills/{name}/files': {
|
|
97
|
+
get: {
|
|
98
|
+
tags: ['config'],
|
|
99
|
+
operationId: 'listSkillFiles',
|
|
100
|
+
summary: 'List files in a skill directory',
|
|
101
|
+
parameters: [
|
|
102
|
+
{
|
|
103
|
+
in: 'path',
|
|
104
|
+
name: 'name',
|
|
105
|
+
required: true,
|
|
106
|
+
schema: { type: 'string' },
|
|
107
|
+
},
|
|
108
|
+
projectQueryParam(),
|
|
109
|
+
],
|
|
110
|
+
responses: {
|
|
111
|
+
200: {
|
|
112
|
+
description: 'OK',
|
|
113
|
+
content: {
|
|
114
|
+
'application/json': {
|
|
115
|
+
schema: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
properties: {
|
|
118
|
+
files: {
|
|
119
|
+
type: 'array',
|
|
120
|
+
items: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
relativePath: { type: 'string' },
|
|
124
|
+
size: { type: 'number' },
|
|
125
|
+
},
|
|
126
|
+
required: ['relativePath', 'size'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
required: ['files'],
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
500: errorResponse(),
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
'/v1/skills/{name}/files/{filePath}': {
|
|
140
|
+
get: {
|
|
141
|
+
tags: ['config'],
|
|
142
|
+
operationId: 'getSkillFile',
|
|
143
|
+
summary: 'Read a specific file from a skill directory',
|
|
144
|
+
parameters: [
|
|
145
|
+
{
|
|
146
|
+
in: 'path',
|
|
147
|
+
name: 'name',
|
|
148
|
+
required: true,
|
|
149
|
+
schema: { type: 'string' },
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
in: 'path',
|
|
153
|
+
name: 'filePath',
|
|
154
|
+
required: true,
|
|
155
|
+
schema: { type: 'string' },
|
|
156
|
+
},
|
|
157
|
+
projectQueryParam(),
|
|
158
|
+
],
|
|
159
|
+
responses: {
|
|
160
|
+
200: {
|
|
161
|
+
description: 'OK',
|
|
162
|
+
content: {
|
|
163
|
+
'application/json': {
|
|
164
|
+
schema: {
|
|
165
|
+
type: 'object',
|
|
166
|
+
properties: {
|
|
167
|
+
content: { type: 'string' },
|
|
168
|
+
path: { type: 'string' },
|
|
169
|
+
},
|
|
170
|
+
required: ['content', 'path'],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
404: errorResponse(),
|
|
176
|
+
500: errorResponse(),
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
96
180
|
'/v1/skills/validate': {
|
|
97
181
|
post: {
|
|
98
182
|
tags: ['config'],
|
package/src/openapi/schemas.ts
CHANGED
|
@@ -13,6 +13,8 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
13
13
|
model?: string;
|
|
14
14
|
toolApproval?: 'auto' | 'dangerous' | 'all';
|
|
15
15
|
guidedMode?: boolean;
|
|
16
|
+
reasoningText?: boolean;
|
|
17
|
+
theme?: string;
|
|
16
18
|
scope?: 'global' | 'local';
|
|
17
19
|
}>();
|
|
18
20
|
|
|
@@ -23,6 +25,8 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
23
25
|
model: string;
|
|
24
26
|
toolApproval: 'auto' | 'dangerous' | 'all';
|
|
25
27
|
guidedMode: boolean;
|
|
28
|
+
reasoningText: boolean;
|
|
29
|
+
theme: string;
|
|
26
30
|
}> = {};
|
|
27
31
|
|
|
28
32
|
if (body.agent) updates.agent = body.agent;
|
|
@@ -30,6 +34,9 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
30
34
|
if (body.model) updates.model = body.model;
|
|
31
35
|
if (body.toolApproval) updates.toolApproval = body.toolApproval;
|
|
32
36
|
if (body.guidedMode !== undefined) updates.guidedMode = body.guidedMode;
|
|
37
|
+
if (body.reasoningText !== undefined)
|
|
38
|
+
updates.reasoningText = body.reasoningText;
|
|
39
|
+
if (body.theme) updates.theme = body.theme;
|
|
33
40
|
|
|
34
41
|
await setConfig(scope, updates, projectRoot);
|
|
35
42
|
|
|
@@ -58,6 +58,8 @@ export function registerMainConfigRoute(app: Hono) {
|
|
|
58
58
|
cfg.defaults.toolApproval,
|
|
59
59
|
) as 'auto' | 'dangerous' | 'all',
|
|
60
60
|
guidedMode: cfg.defaults.guidedMode ?? false,
|
|
61
|
+
reasoningText: cfg.defaults.reasoningText ?? true,
|
|
62
|
+
theme: cfg.defaults.theme,
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
return c.json({
|
|
@@ -122,7 +122,8 @@ export function registerSessionMessagesRoutes(app: Hono) {
|
|
|
122
122
|
typeOf: typeof userContext,
|
|
123
123
|
});
|
|
124
124
|
|
|
125
|
-
const reasoning =
|
|
125
|
+
const reasoning =
|
|
126
|
+
body?.reasoningText ?? cfg.defaults.reasoningText ?? false;
|
|
126
127
|
|
|
127
128
|
// Validate model capabilities if tools are allowed for this agent
|
|
128
129
|
const wantsToolCalls = true; // agent toolset may be non-empty
|
package/src/routes/sessions.ts
CHANGED
|
@@ -140,11 +140,16 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
140
140
|
agent?: string;
|
|
141
141
|
provider?: string;
|
|
142
142
|
model?: string;
|
|
143
|
+
title?: string | null;
|
|
143
144
|
lastActiveAt?: number;
|
|
144
145
|
} = {
|
|
145
146
|
lastActiveAt: Date.now(),
|
|
146
147
|
};
|
|
147
148
|
|
|
149
|
+
if (typeof body.title === 'string') {
|
|
150
|
+
updates.title = body.title.trim() || null;
|
|
151
|
+
}
|
|
152
|
+
|
|
148
153
|
// Validate agent if provided
|
|
149
154
|
if (typeof body.agent === 'string') {
|
|
150
155
|
const agentName = body.agent.trim();
|
package/src/routes/skills.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type { Hono } from 'hono';
|
|
|
2
2
|
import {
|
|
3
3
|
discoverSkills,
|
|
4
4
|
loadSkill,
|
|
5
|
+
loadSkillFile,
|
|
6
|
+
discoverSkillFiles,
|
|
5
7
|
findGitRoot,
|
|
6
8
|
validateSkillName,
|
|
7
9
|
parseSkillFile,
|
|
@@ -61,6 +63,51 @@ export function registerSkillsRoutes(app: Hono) {
|
|
|
61
63
|
});
|
|
62
64
|
|
|
63
65
|
app.post('/v1/skills/validate', async (c) => {
|
|
66
|
+
app.get('/v1/skills/:name/files', async (c) => {
|
|
67
|
+
try {
|
|
68
|
+
const name = c.req.param('name');
|
|
69
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
70
|
+
const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
|
|
71
|
+
await discoverSkills(projectRoot, repoRoot);
|
|
72
|
+
|
|
73
|
+
const files = await discoverSkillFiles(name);
|
|
74
|
+
return c.json({ files });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error('Failed to list skill files', error);
|
|
77
|
+
const errorResponse = serializeError(error);
|
|
78
|
+
return c.json(
|
|
79
|
+
errorResponse,
|
|
80
|
+
(errorResponse.error.status || 500) as 500,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
app.get('/v1/skills/:name/files/*', async (c) => {
|
|
86
|
+
try {
|
|
87
|
+
const name = c.req.param('name');
|
|
88
|
+
const filePath = c.req.path.replace(`/v1/skills/${name}/files/`, '');
|
|
89
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
90
|
+
const repoRoot = (await findGitRoot(projectRoot)) ?? projectRoot;
|
|
91
|
+
await discoverSkills(projectRoot, repoRoot);
|
|
92
|
+
|
|
93
|
+
const result = await loadSkillFile(name, filePath);
|
|
94
|
+
if (!result) {
|
|
95
|
+
return c.json(
|
|
96
|
+
{ error: `File '${filePath}' not found in skill '${name}'` },
|
|
97
|
+
404,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return c.json({ content: result.content, path: result.resolvedPath });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.error('Failed to load skill file', error);
|
|
103
|
+
const errorResponse = serializeError(error);
|
|
104
|
+
return c.json(
|
|
105
|
+
errorResponse,
|
|
106
|
+
(errorResponse.error.status || 500) as 500,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
64
111
|
try {
|
|
65
112
|
const body = await c.req.json<{ content: string; path?: string }>();
|
|
66
113
|
if (!body.content) {
|
|
@@ -12,7 +12,7 @@ import { ToolHistoryTracker } from './tool-history-tracker.ts';
|
|
|
12
12
|
export async function buildHistoryMessages(
|
|
13
13
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
14
14
|
sessionId: string,
|
|
15
|
-
|
|
15
|
+
_currentMessageId?: string,
|
|
16
16
|
): Promise<ModelMessage[]> {
|
|
17
17
|
const rows = await db
|
|
18
18
|
.select()
|
|
@@ -24,26 +24,30 @@ export async function buildHistoryMessages(
|
|
|
24
24
|
const toolHistory = new ToolHistoryTracker();
|
|
25
25
|
|
|
26
26
|
for (const m of rows) {
|
|
27
|
+
const parts = await db
|
|
28
|
+
.select()
|
|
29
|
+
.from(messageParts)
|
|
30
|
+
.where(eq(messageParts.messageId, m.id))
|
|
31
|
+
.orderBy(asc(messageParts.index));
|
|
32
|
+
|
|
27
33
|
if (
|
|
28
34
|
m.role === 'assistant' &&
|
|
29
35
|
m.status !== 'complete' &&
|
|
30
36
|
m.status !== 'completed' &&
|
|
31
|
-
m.status !== 'error'
|
|
32
|
-
m.id !== currentMessageId
|
|
37
|
+
m.status !== 'error'
|
|
33
38
|
) {
|
|
39
|
+
if (parts.length === 0) {
|
|
40
|
+
debugLog(
|
|
41
|
+
`[buildHistoryMessages] Skipping empty assistant message ${m.id} with status ${m.status}`,
|
|
42
|
+
);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
34
46
|
debugLog(
|
|
35
|
-
`[buildHistoryMessages]
|
|
47
|
+
`[buildHistoryMessages] Including non-complete assistant message ${m.id} (status: ${m.status}) with ${parts.length} parts to preserve context`,
|
|
36
48
|
);
|
|
37
|
-
logPendingToolParts(db, m.id);
|
|
38
|
-
continue;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
|
-
const parts = await db
|
|
42
|
-
.select()
|
|
43
|
-
.from(messageParts)
|
|
44
|
-
.where(eq(messageParts.messageId, m.id))
|
|
45
|
-
.orderBy(asc(messageParts.index));
|
|
46
|
-
|
|
47
51
|
if (m.role === 'user') {
|
|
48
52
|
const uparts: UIMessage['parts'] = [];
|
|
49
53
|
for (const p of parts) {
|
|
@@ -123,7 +127,6 @@ export async function buildHistoryMessages(
|
|
|
123
127
|
if (t) assistantParts.push({ type: 'text', text: t });
|
|
124
128
|
} catch {}
|
|
125
129
|
} else if (p.type === 'tool_call') {
|
|
126
|
-
// Skip compacted tool calls entirely
|
|
127
130
|
if (p.compactedAt) continue;
|
|
128
131
|
|
|
129
132
|
try {
|
|
@@ -141,7 +144,6 @@ export async function buildHistoryMessages(
|
|
|
141
144
|
}
|
|
142
145
|
} catch {}
|
|
143
146
|
} else if (p.type === 'tool_result') {
|
|
144
|
-
// Skip compacted tool results entirely
|
|
145
147
|
if (p.compactedAt) continue;
|
|
146
148
|
|
|
147
149
|
try {
|
|
@@ -159,7 +161,6 @@ export async function buildHistoryMessages(
|
|
|
159
161
|
}
|
|
160
162
|
} catch {}
|
|
161
163
|
}
|
|
162
|
-
// Skip error parts in history
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
const toolResultsById = new Map(
|
|
@@ -167,14 +168,12 @@ export async function buildHistoryMessages(
|
|
|
167
168
|
);
|
|
168
169
|
|
|
169
170
|
for (const call of toolCalls) {
|
|
170
|
-
// Skip finish tool from history - it's internal loop control
|
|
171
171
|
if (call.name === 'finish') continue;
|
|
172
172
|
|
|
173
173
|
const toolType = `tool-${call.name}` as `tool-${string}`;
|
|
174
174
|
let result = toolResultsById.get(call.callId);
|
|
175
175
|
|
|
176
176
|
if (!result) {
|
|
177
|
-
// Synthesize a result for incomplete tool calls to preserve history
|
|
178
177
|
debugLog(
|
|
179
178
|
`[buildHistoryMessages] Synthesizing error result for incomplete tool call ${call.name}#${call.callId}`,
|
|
180
179
|
);
|
|
@@ -221,7 +220,7 @@ export async function buildHistoryMessages(
|
|
|
221
220
|
return await convertToModelMessages(ui);
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
async function
|
|
223
|
+
async function _logPendingToolParts(
|
|
225
224
|
db: Awaited<ReturnType<typeof getDb>>,
|
|
226
225
|
messageId: string,
|
|
227
226
|
) {
|
|
@@ -99,7 +99,7 @@ export function toClaudeCodeName(canonical: string): string {
|
|
|
99
99
|
}
|
|
100
100
|
// Default: convert snake_case to PascalCase
|
|
101
101
|
return canonical
|
|
102
|
-
.split(
|
|
102
|
+
.split(/[\s_]+/)
|
|
103
103
|
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
104
104
|
.join('');
|
|
105
105
|
}
|