@ottocode/server 0.1.226 → 0.1.227
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/events/types.ts +2 -0
- package/src/hono-context.d.ts +7 -0
- package/src/index.ts +9 -1
- package/src/routes/ask.ts +5 -3
- package/src/routes/config/agents.ts +5 -3
- package/src/routes/config/defaults.ts +3 -3
- package/src/routes/config/main.ts +5 -3
- package/src/routes/config/models.ts +10 -6
- package/src/routes/config/providers.ts +8 -4
- package/src/routes/config/utils.ts +11 -4
- package/src/routes/terminals.ts +6 -4
- package/src/routes/tunnel.ts +1 -1
- package/src/runtime/agent/runner.ts +21 -7
- package/src/runtime/ask/service.ts +5 -0
- package/src/runtime/errors/api-error.ts +29 -21
- package/src/runtime/message/service.ts +2 -2
- package/src/runtime/session/branch.ts +11 -1
- package/src/runtime/session/manager.ts +6 -1
- package/src/runtime/stream/step-finish.ts +3 -0
- package/src/tools/adapter.ts +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.227",
|
|
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.227",
|
|
53
|
+
"@ottocode/database": "0.1.227",
|
|
54
54
|
"drizzle-orm": "^0.44.5",
|
|
55
55
|
"hono": "^4.9.9",
|
|
56
56
|
"zod": "^4.3.6"
|
package/src/events/types.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type OttoEventType =
|
|
2
2
|
| 'tool.approval.required'
|
|
3
|
+
| 'tool.approval.updated'
|
|
3
4
|
| 'tool.approval.resolved'
|
|
4
5
|
| 'setu.payment.required'
|
|
5
6
|
| 'setu.payment.signing'
|
|
@@ -11,6 +12,7 @@ export type OttoEventType =
|
|
|
11
12
|
| 'setu.fiat.checkout_created'
|
|
12
13
|
| 'setu.balance.updated'
|
|
13
14
|
| 'session.created'
|
|
15
|
+
| 'session.deleted'
|
|
14
16
|
| 'session.updated'
|
|
15
17
|
| 'message.created'
|
|
16
18
|
| 'message.updated'
|
package/src/index.ts
CHANGED
|
@@ -191,6 +191,7 @@ export type EmbeddedAppConfig = {
|
|
|
191
191
|
provider?: ProviderId;
|
|
192
192
|
model?: string;
|
|
193
193
|
agent?: string;
|
|
194
|
+
toolApproval?: 'auto' | 'dangerous' | 'all';
|
|
194
195
|
};
|
|
195
196
|
/** Additional CORS origins for proxies/Tailscale (e.g., ['https://myapp.ts.net', 'https://example.com']) */
|
|
196
197
|
corsOrigins?: string[];
|
|
@@ -202,7 +203,14 @@ export function createEmbeddedApp(config: EmbeddedAppConfig = {}) {
|
|
|
202
203
|
// Store injected config in Hono context for routes to access
|
|
203
204
|
// Config can be empty - routes will fall back to files/env
|
|
204
205
|
honoApp.use('*', async (c, next) => {
|
|
205
|
-
|
|
206
|
+
(
|
|
207
|
+
c as unknown as {
|
|
208
|
+
set: (
|
|
209
|
+
key: 'embeddedConfig',
|
|
210
|
+
value: EmbeddedAppConfig | undefined,
|
|
211
|
+
) => void;
|
|
212
|
+
}
|
|
213
|
+
).set('embeddedConfig', config);
|
|
206
214
|
await next();
|
|
207
215
|
});
|
|
208
216
|
|
package/src/routes/ask.ts
CHANGED
|
@@ -21,9 +21,11 @@ export function registerAskRoutes(app: Hono) {
|
|
|
21
21
|
return c.json({ error: 'Prompt is required.' }, 400);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const embeddedConfig =
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const embeddedConfig = (
|
|
25
|
+
c as unknown as {
|
|
26
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
27
|
+
}
|
|
28
|
+
).get('embeddedConfig');
|
|
27
29
|
|
|
28
30
|
// Hybrid fallback: Use embedded config if provided, otherwise fall back to files/env
|
|
29
31
|
let injectableConfig: InjectableConfig | undefined;
|
|
@@ -8,9 +8,11 @@ import { discoverAllAgents, getDefault } from './utils.ts';
|
|
|
8
8
|
export function registerAgentsRoute(app: Hono) {
|
|
9
9
|
app.get('/v1/config/agents', async (c) => {
|
|
10
10
|
try {
|
|
11
|
-
const embeddedConfig =
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const embeddedConfig = (
|
|
12
|
+
c as unknown as {
|
|
13
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
14
|
+
}
|
|
15
|
+
).get('embeddedConfig');
|
|
14
16
|
|
|
15
17
|
if (embeddedConfig) {
|
|
16
18
|
const agents = embeddedConfig.agents
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
|
-
import { setConfig, loadConfig } from '@ottocode/sdk';
|
|
2
|
+
import { setConfig, loadConfig, type ProviderId } from '@ottocode/sdk';
|
|
3
3
|
import { logger } from '@ottocode/sdk';
|
|
4
4
|
import { serializeError } from '../../runtime/errors/api-error.ts';
|
|
5
5
|
|
|
@@ -21,7 +21,7 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
21
21
|
const scope = body.scope || 'global';
|
|
22
22
|
const updates: Partial<{
|
|
23
23
|
agent: string;
|
|
24
|
-
provider:
|
|
24
|
+
provider: ProviderId;
|
|
25
25
|
model: string;
|
|
26
26
|
toolApproval: 'auto' | 'dangerous' | 'all';
|
|
27
27
|
guidedMode: boolean;
|
|
@@ -30,7 +30,7 @@ export function registerDefaultsRoute(app: Hono) {
|
|
|
30
30
|
}> = {};
|
|
31
31
|
|
|
32
32
|
if (body.agent) updates.agent = body.agent;
|
|
33
|
-
if (body.provider) updates.provider = body.provider;
|
|
33
|
+
if (body.provider) updates.provider = body.provider as ProviderId;
|
|
34
34
|
if (body.model) updates.model = body.model;
|
|
35
35
|
if (body.toolApproval) updates.toolApproval = body.toolApproval;
|
|
36
36
|
if (body.guidedMode !== undefined) updates.guidedMode = body.guidedMode;
|
|
@@ -13,9 +13,11 @@ export function registerMainConfigRoute(app: Hono) {
|
|
|
13
13
|
app.get('/v1/config', async (c) => {
|
|
14
14
|
try {
|
|
15
15
|
const projectRoot = c.req.query('project') || process.cwd();
|
|
16
|
-
const embeddedConfig =
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
const embeddedConfig = (
|
|
17
|
+
c as unknown as {
|
|
18
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
19
|
+
}
|
|
20
|
+
).get('embeddedConfig');
|
|
19
21
|
|
|
20
22
|
const cfg = await loadConfig(projectRoot);
|
|
21
23
|
|
|
@@ -82,9 +82,11 @@ async function getAuthorizedCopilotModels(
|
|
|
82
82
|
export function registerModelsRoutes(app: Hono) {
|
|
83
83
|
app.get('/v1/config/providers/:provider/models', async (c) => {
|
|
84
84
|
try {
|
|
85
|
-
const embeddedConfig =
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
const embeddedConfig = (
|
|
86
|
+
c as unknown as {
|
|
87
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
88
|
+
}
|
|
89
|
+
).get('embeddedConfig');
|
|
88
90
|
const provider = c.req.param('provider') as ProviderId;
|
|
89
91
|
|
|
90
92
|
const projectRoot = c.req.query('project') || process.cwd();
|
|
@@ -152,9 +154,11 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
152
154
|
|
|
153
155
|
app.get('/v1/config/models', async (c) => {
|
|
154
156
|
try {
|
|
155
|
-
const embeddedConfig =
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
const embeddedConfig = (
|
|
158
|
+
c as unknown as {
|
|
159
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
160
|
+
}
|
|
161
|
+
).get('embeddedConfig');
|
|
158
162
|
|
|
159
163
|
const projectRoot = c.req.query('project') || process.cwd();
|
|
160
164
|
const cfg = await loadConfig(projectRoot);
|
|
@@ -9,14 +9,18 @@ import { getAuthorizedProviders, getDefault } from './utils.ts';
|
|
|
9
9
|
export function registerProvidersRoute(app: Hono) {
|
|
10
10
|
app.get('/v1/config/providers', async (c) => {
|
|
11
11
|
try {
|
|
12
|
-
const embeddedConfig =
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const embeddedConfig = (
|
|
13
|
+
c as unknown as {
|
|
14
|
+
get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
|
|
15
|
+
}
|
|
16
|
+
).get('embeddedConfig');
|
|
15
17
|
|
|
16
18
|
if (embeddedConfig) {
|
|
17
19
|
const providers = embeddedConfig.auth
|
|
18
20
|
? (Object.keys(embeddedConfig.auth) as ProviderId[])
|
|
19
|
-
:
|
|
21
|
+
: embeddedConfig.provider
|
|
22
|
+
? [embeddedConfig.provider]
|
|
23
|
+
: [];
|
|
20
24
|
|
|
21
25
|
return c.json({
|
|
22
26
|
providers,
|
|
@@ -63,7 +63,8 @@ export async function getAuthTypeForProvider(
|
|
|
63
63
|
projectRoot: string,
|
|
64
64
|
): Promise<'api' | 'oauth' | 'wallet' | undefined> {
|
|
65
65
|
if (embeddedConfig?.auth?.[provider]) {
|
|
66
|
-
|
|
66
|
+
const embeddedAuth = embeddedConfig.auth[provider];
|
|
67
|
+
return 'type' in embeddedAuth ? embeddedAuth.type : 'api';
|
|
67
68
|
}
|
|
68
69
|
const auth = await getAuth(provider, projectRoot);
|
|
69
70
|
return auth?.type as 'api' | 'oauth' | 'wallet' | undefined;
|
|
@@ -83,7 +84,9 @@ export async function discoverAllAgents(
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
} catch (err) {
|
|
86
|
-
logger.debug('Failed to load agents.json',
|
|
87
|
+
logger.debug('Failed to load agents.json', {
|
|
88
|
+
error: err instanceof Error ? err.message : String(err),
|
|
89
|
+
});
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
try {
|
|
@@ -98,7 +101,9 @@ export async function discoverAllAgents(
|
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
103
|
} catch (err) {
|
|
101
|
-
logger.debug('Failed to read local agents directory',
|
|
104
|
+
logger.debug('Failed to read local agents directory', {
|
|
105
|
+
error: err instanceof Error ? err.message : String(err),
|
|
106
|
+
});
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
try {
|
|
@@ -113,7 +118,9 @@ export async function discoverAllAgents(
|
|
|
113
118
|
}
|
|
114
119
|
}
|
|
115
120
|
} catch (err) {
|
|
116
|
-
logger.debug('Failed to read global agents directory',
|
|
121
|
+
logger.debug('Failed to read global agents directory', {
|
|
122
|
+
error: err instanceof Error ? err.message : String(err),
|
|
123
|
+
});
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
return Array.from(agentSet).sort();
|
package/src/routes/terminals.ts
CHANGED
|
@@ -87,12 +87,14 @@ export function registerTerminalsRoutes(
|
|
|
87
87
|
return c.json({ error: 'Terminal not found' }, 404);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
const activeTerminal = terminal;
|
|
91
|
+
|
|
90
92
|
return streamSSE(c, async (stream) => {
|
|
91
93
|
logger.debug('SSE stream started for terminal', { id });
|
|
92
94
|
// Send historical buffer first (unless skipHistory is set)
|
|
93
95
|
const skipHistory = c.req.query('skipHistory') === 'true';
|
|
94
96
|
if (!skipHistory) {
|
|
95
|
-
const history =
|
|
97
|
+
const history = activeTerminal.read();
|
|
96
98
|
logger.debug('SSE sending terminal history', {
|
|
97
99
|
id,
|
|
98
100
|
lines: history.length,
|
|
@@ -120,8 +122,8 @@ export function registerTerminalsRoutes(
|
|
|
120
122
|
let finished = false;
|
|
121
123
|
|
|
122
124
|
function cleanup() {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
activeTerminal.removeDataListener(onData);
|
|
126
|
+
activeTerminal.removeExitListener(onExit);
|
|
125
127
|
c.req.raw.signal.removeEventListener('abort', onAbort);
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -145,7 +147,7 @@ export function registerTerminalsRoutes(
|
|
|
145
147
|
|
|
146
148
|
function onAbort() {
|
|
147
149
|
logger.debug('SSE client disconnected from terminal', {
|
|
148
|
-
id:
|
|
150
|
+
id: activeTerminal.id,
|
|
149
151
|
});
|
|
150
152
|
stream.close();
|
|
151
153
|
finish();
|
package/src/routes/tunnel.ts
CHANGED
|
@@ -59,7 +59,7 @@ export function registerTunnelRoutes(app: Hono) {
|
|
|
59
59
|
|
|
60
60
|
const url = await activeTunnel.start(port, (msg) => {
|
|
61
61
|
progressMessage = msg;
|
|
62
|
-
logger.debug('Tunnel progress
|
|
62
|
+
logger.debug('Tunnel progress', { message: msg });
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
tunnelUrl = url;
|
|
@@ -183,11 +183,15 @@ async function runAssistant(opts: RunOpts) {
|
|
|
183
183
|
let _toolActivityObserved = false;
|
|
184
184
|
let _trailingAssistantTextAfterTool = false;
|
|
185
185
|
let _abortedByUser = false;
|
|
186
|
+
let titleGenerationTriggered = false;
|
|
186
187
|
const unsubscribeFinish = subscribe(opts.sessionId, (evt) => {
|
|
187
188
|
if (evt.type === 'tool.call' || evt.type === 'tool.result') {
|
|
188
189
|
_toolActivityObserved = true;
|
|
189
190
|
_trailingAssistantTextAfterTool = false;
|
|
190
191
|
}
|
|
192
|
+
if (evt.type === 'tool.call') {
|
|
193
|
+
triggerTitleGenerationWhenReady();
|
|
194
|
+
}
|
|
191
195
|
if (evt.type !== 'tool.result') return;
|
|
192
196
|
try {
|
|
193
197
|
const name = (evt.payload as { name?: string } | undefined)?.name;
|
|
@@ -222,6 +226,22 @@ async function runAssistant(opts: RunOpts) {
|
|
|
222
226
|
stepIndex += 1;
|
|
223
227
|
return stepIndex;
|
|
224
228
|
};
|
|
229
|
+
const triggerTitleGenerationWhenReady = () => {
|
|
230
|
+
if (titleGenerationTriggered) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
titleGenerationTriggered = true;
|
|
235
|
+
if (!isFirstMessage) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
void triggerDeferredTitleGeneration({
|
|
240
|
+
cfg,
|
|
241
|
+
db,
|
|
242
|
+
sessionId: opts.sessionId,
|
|
243
|
+
});
|
|
244
|
+
};
|
|
225
245
|
|
|
226
246
|
const reasoningStates = new Map<string, ReasoningState>();
|
|
227
247
|
|
|
@@ -233,6 +253,7 @@ async function runAssistant(opts: RunOpts) {
|
|
|
233
253
|
getCurrentPartId,
|
|
234
254
|
updateCurrentPartId,
|
|
235
255
|
updateAccumulated,
|
|
256
|
+
triggerTitleGenerationWhenReady,
|
|
236
257
|
sharedCtx,
|
|
237
258
|
updateSessionTokensIncremental,
|
|
238
259
|
updateMessageTokensIncremental,
|
|
@@ -315,13 +336,6 @@ async function runAssistant(opts: RunOpts) {
|
|
|
315
336
|
if (!firstDeltaSeen) {
|
|
316
337
|
firstDeltaSeen = true;
|
|
317
338
|
streamStartTimer.end();
|
|
318
|
-
if (isFirstMessage) {
|
|
319
|
-
void triggerDeferredTitleGeneration({
|
|
320
|
-
cfg,
|
|
321
|
-
db,
|
|
322
|
-
sessionId: opts.sessionId,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
339
|
}
|
|
326
340
|
|
|
327
341
|
if (!currentPartId) {
|
|
@@ -127,7 +127,12 @@ async function processAskRequest(
|
|
|
127
127
|
google: { enabled: true },
|
|
128
128
|
openrouter: { enabled: true },
|
|
129
129
|
opencode: { enabled: true },
|
|
130
|
+
copilot: { enabled: true },
|
|
130
131
|
setu: { enabled: true },
|
|
132
|
+
zai: { enabled: true },
|
|
133
|
+
'zai-coding': { enabled: true },
|
|
134
|
+
moonshot: { enabled: true },
|
|
135
|
+
minimax: { enabled: true },
|
|
131
136
|
},
|
|
132
137
|
paths: {
|
|
133
138
|
dataDir: `${projectRoot}/.otto`,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* across all API endpoints.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
8
9
|
import { isDebugEnabled } from '../debug/state.ts';
|
|
9
10
|
import { toErrorPayload } from './handling.ts';
|
|
10
11
|
|
|
@@ -16,7 +17,7 @@ export type APIErrorResponse = {
|
|
|
16
17
|
message: string;
|
|
17
18
|
type: string;
|
|
18
19
|
code?: string;
|
|
19
|
-
status?:
|
|
20
|
+
status?: ContentfulStatusCode;
|
|
20
21
|
details?: Record<string, unknown>;
|
|
21
22
|
stack?: string;
|
|
22
23
|
};
|
|
@@ -27,29 +28,33 @@ export type APIErrorResponse = {
|
|
|
27
28
|
*/
|
|
28
29
|
export class APIError extends Error {
|
|
29
30
|
public readonly code?: string;
|
|
30
|
-
public readonly status:
|
|
31
|
+
public readonly status: ContentfulStatusCode;
|
|
31
32
|
public readonly type: string;
|
|
32
33
|
public readonly details?: Record<string, unknown>;
|
|
33
34
|
|
|
34
35
|
constructor(
|
|
35
36
|
message: string,
|
|
36
|
-
options?:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
options?:
|
|
38
|
+
| ContentfulStatusCode
|
|
39
|
+
| {
|
|
40
|
+
code?: string;
|
|
41
|
+
status?: ContentfulStatusCode;
|
|
42
|
+
type?: string;
|
|
43
|
+
details?: Record<string, unknown>;
|
|
44
|
+
cause?: unknown;
|
|
45
|
+
},
|
|
43
46
|
) {
|
|
44
47
|
super(message);
|
|
45
48
|
this.name = 'APIError';
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.
|
|
49
|
-
this.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const normalizedOptions =
|
|
50
|
+
typeof options === 'number' ? { status: options } : options;
|
|
51
|
+
this.code = normalizedOptions?.code;
|
|
52
|
+
this.status = normalizedOptions?.status ?? 500;
|
|
53
|
+
this.type = normalizedOptions?.type ?? 'api_error';
|
|
54
|
+
this.details = normalizedOptions?.details;
|
|
55
|
+
|
|
56
|
+
if (normalizedOptions?.cause) {
|
|
57
|
+
this.cause = normalizedOptions.cause;
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
// Maintain proper stack trace
|
|
@@ -72,7 +77,7 @@ export function serializeError(err: unknown): APIErrorResponse {
|
|
|
72
77
|
// Determine HTTP status code
|
|
73
78
|
// Default to 400 for generic errors (client errors)
|
|
74
79
|
// Only use 500 if explicitly set or for APIError instances without a status
|
|
75
|
-
let status = 400;
|
|
80
|
+
let status: ContentfulStatusCode = 400;
|
|
76
81
|
|
|
77
82
|
// Handle APIError instances first
|
|
78
83
|
if (err instanceof APIError) {
|
|
@@ -80,15 +85,16 @@ export function serializeError(err: unknown): APIErrorResponse {
|
|
|
80
85
|
} else if (err && typeof err === 'object') {
|
|
81
86
|
const errObj = err as Record<string, unknown>;
|
|
82
87
|
if (typeof errObj.status === 'number') {
|
|
83
|
-
status = errObj.status;
|
|
88
|
+
status = errObj.status as ContentfulStatusCode;
|
|
84
89
|
} else if (typeof errObj.statusCode === 'number') {
|
|
85
|
-
status = errObj.statusCode;
|
|
90
|
+
status = errObj.statusCode as ContentfulStatusCode;
|
|
86
91
|
} else if (
|
|
87
92
|
errObj.details &&
|
|
88
93
|
typeof errObj.details === 'object' &&
|
|
89
94
|
typeof (errObj.details as Record<string, unknown>).statusCode === 'number'
|
|
90
95
|
) {
|
|
91
|
-
status = (errObj.details as Record<string, unknown>)
|
|
96
|
+
status = (errObj.details as Record<string, unknown>)
|
|
97
|
+
.statusCode as ContentfulStatusCode;
|
|
92
98
|
}
|
|
93
99
|
}
|
|
94
100
|
|
|
@@ -130,7 +136,9 @@ export function serializeError(err: unknown): APIErrorResponse {
|
|
|
130
136
|
* @param err - The error to convert
|
|
131
137
|
* @returns Tuple of [APIErrorResponse, HTTP status code]
|
|
132
138
|
*/
|
|
133
|
-
export function createErrorResponse(
|
|
139
|
+
export function createErrorResponse(
|
|
140
|
+
err: unknown,
|
|
141
|
+
): [APIErrorResponse, ContentfulStatusCode] {
|
|
134
142
|
const response = serializeError(err);
|
|
135
143
|
return [response, response.error.status ?? 500];
|
|
136
144
|
}
|
|
@@ -284,7 +284,7 @@ async function generateSessionTitle(args: {
|
|
|
284
284
|
return;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
const provider = sess.provider ?? cfg.defaults.provider;
|
|
287
|
+
const provider = (sess.provider ?? cfg.defaults.provider) as ProviderId;
|
|
288
288
|
const modelName = sess.model ?? cfg.defaults.model;
|
|
289
289
|
|
|
290
290
|
debugLog('[TITLE_GEN] Generating title for session');
|
|
@@ -365,7 +365,7 @@ Output ONLY the title, nothing else.`;
|
|
|
365
365
|
|
|
366
366
|
await db
|
|
367
367
|
.update(sessions)
|
|
368
|
-
.set({ title: sanitized,
|
|
368
|
+
.set({ title: sanitized, lastActiveAt: Date.now() })
|
|
369
369
|
.where(eq(sessions.id, sessionId));
|
|
370
370
|
|
|
371
371
|
debugLog(`[TITLE_GEN] Setting final title: "${sanitized}"`);
|
|
@@ -160,7 +160,14 @@ export async function createBranch({
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
const result: SessionRow = {
|
|
163
|
-
|
|
163
|
+
id: newSession.id,
|
|
164
|
+
title: newSession.title ?? null,
|
|
165
|
+
agent: newSession.agent,
|
|
166
|
+
provider: newSession.provider,
|
|
167
|
+
model: newSession.model,
|
|
168
|
+
projectPath: newSession.projectPath,
|
|
169
|
+
createdAt: newSession.createdAt,
|
|
170
|
+
lastActiveAt: newSession.lastActiveAt ?? null,
|
|
164
171
|
totalInputTokens: null,
|
|
165
172
|
totalOutputTokens: null,
|
|
166
173
|
totalCachedTokens: null,
|
|
@@ -171,6 +178,9 @@ export async function createBranch({
|
|
|
171
178
|
currentContextTokens: null,
|
|
172
179
|
contextSummary: null,
|
|
173
180
|
lastCompactedAt: null,
|
|
181
|
+
parentSessionId: newSession.parentSessionId ?? null,
|
|
182
|
+
branchPointMessageId: newSession.branchPointMessageId ?? null,
|
|
183
|
+
sessionType: newSession.sessionType ?? null,
|
|
174
184
|
};
|
|
175
185
|
|
|
176
186
|
publish({
|
|
@@ -39,7 +39,7 @@ export async function createSession({
|
|
|
39
39
|
await ensureProviderEnv(cfg, provider);
|
|
40
40
|
const id = crypto.randomUUID();
|
|
41
41
|
const now = Date.now();
|
|
42
|
-
const row = {
|
|
42
|
+
const row: SessionRow = {
|
|
43
43
|
id,
|
|
44
44
|
title: title ?? null,
|
|
45
45
|
agent,
|
|
@@ -56,6 +56,11 @@ export async function createSession({
|
|
|
56
56
|
totalToolTimeMs: null,
|
|
57
57
|
toolCountsJson: null,
|
|
58
58
|
currentContextTokens: null,
|
|
59
|
+
contextSummary: null,
|
|
60
|
+
lastCompactedAt: null,
|
|
61
|
+
parentSessionId: null,
|
|
62
|
+
branchPointMessageId: null,
|
|
63
|
+
sessionType: 'main',
|
|
59
64
|
};
|
|
60
65
|
await db.insert(sessions).values(row);
|
|
61
66
|
publish({ type: 'session.created', sessionId: id, payload: row });
|
|
@@ -16,6 +16,7 @@ export function createStepFinishHandler(
|
|
|
16
16
|
getCurrentPartId: () => string | null,
|
|
17
17
|
updateCurrentPartId: (id: string | null) => void,
|
|
18
18
|
updateAccumulated: (text: string) => void,
|
|
19
|
+
triggerTitleGenerationWhenReady: () => void,
|
|
19
20
|
sharedCtx: ToolAdapterContext,
|
|
20
21
|
updateSessionTokensIncrementalFn: (
|
|
21
22
|
usage: UsageData,
|
|
@@ -31,6 +32,8 @@ export function createStepFinishHandler(
|
|
|
31
32
|
) => Promise<void>,
|
|
32
33
|
) {
|
|
33
34
|
return async (step: StepFinishEvent) => {
|
|
35
|
+
triggerTitleGenerationWhenReady();
|
|
36
|
+
|
|
34
37
|
const finishedAt = Date.now();
|
|
35
38
|
const currentPartId = getCurrentPartId();
|
|
36
39
|
const stepIndex = getStepIndex();
|
package/src/tools/adapter.ts
CHANGED
|
@@ -130,12 +130,13 @@ export function adaptTools(
|
|
|
130
130
|
}: {
|
|
131
131
|
callId?: string;
|
|
132
132
|
startTs?: number;
|
|
133
|
-
stepIndexForEvent
|
|
133
|
+
stepIndexForEvent?: number;
|
|
134
134
|
args?: unknown;
|
|
135
135
|
},
|
|
136
136
|
) => {
|
|
137
137
|
const resultPartId = crypto.randomUUID();
|
|
138
138
|
const endTs = Date.now();
|
|
139
|
+
const effectiveStepIndex = stepIndexForEvent ?? ctx.stepIndex;
|
|
139
140
|
const dur =
|
|
140
141
|
typeof startTs === 'number' ? Math.max(0, endTs - startTs) : null;
|
|
141
142
|
|
|
@@ -160,7 +161,7 @@ export function adaptTools(
|
|
|
160
161
|
id: resultPartId,
|
|
161
162
|
messageId: ctx.messageId,
|
|
162
163
|
index,
|
|
163
|
-
stepIndex:
|
|
164
|
+
stepIndex: effectiveStepIndex,
|
|
164
165
|
type: 'tool_result',
|
|
165
166
|
content: JSON.stringify(contentObj),
|
|
166
167
|
agent: ctx.agent,
|
|
@@ -176,7 +177,7 @@ export function adaptTools(
|
|
|
176
177
|
publish({
|
|
177
178
|
type: 'tool.result',
|
|
178
179
|
sessionId: ctx.sessionId,
|
|
179
|
-
payload: { ...contentObj, stepIndex:
|
|
180
|
+
payload: { ...contentObj, stepIndex: effectiveStepIndex },
|
|
180
181
|
});
|
|
181
182
|
};
|
|
182
183
|
|