@ottocode/server 0.1.233 → 0.1.235

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.
@@ -5,70 +5,7 @@
5
5
  * centralized debug-state and logger modules.
6
6
  */
7
7
 
8
- import { isDebugEnabled as isDebugEnabledNew } from './state.ts';
9
- import { time as timeNew, debug as debugNew } from '@ottocode/sdk';
10
-
11
- const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
12
-
13
- const SYNONYMS: Record<string, string> = {
14
- debug: 'log',
15
- logs: 'log',
16
- logging: 'log',
17
- trace: 'log',
18
- verbose: 'log',
19
- log: 'log',
20
- time: 'timing',
21
- timing: 'timing',
22
- timings: 'timing',
23
- perf: 'timing',
24
- };
25
-
26
- type DebugConfig = { flags: Set<string> };
27
-
28
- let cachedConfig: DebugConfig | null = null;
29
-
30
- function isTruthy(raw: string | undefined): boolean {
31
- if (!raw) return false;
32
- const trimmed = raw.trim().toLowerCase();
33
- if (!trimmed) return false;
34
- return TRUTHY.has(trimmed) || trimmed === 'all';
35
- }
36
-
37
- function normalizeToken(token: string): string {
38
- const trimmed = token.trim().toLowerCase();
39
- if (!trimmed) return '';
40
- if (TRUTHY.has(trimmed) || trimmed === 'all') return 'all';
41
- return SYNONYMS[trimmed] ?? trimmed;
42
- }
43
-
44
- function parseDebugConfig(): DebugConfig {
45
- const flags = new Set<string>();
46
- const sources = [process.env.OTTO_DEBUG, process.env.DEBUG_OTTO];
47
- let sawValue = false;
48
- for (const raw of sources) {
49
- if (typeof raw !== 'string') continue;
50
- const trimmed = raw.trim();
51
- if (!trimmed) continue;
52
- sawValue = true;
53
- const tokens = trimmed.split(/[\s,]+/);
54
- let matched = false;
55
- for (const token of tokens) {
56
- const normalized = normalizeToken(token);
57
- if (!normalized) continue;
58
- matched = true;
59
- flags.add(normalized);
60
- }
61
- if (!matched && isTruthy(trimmed)) flags.add('all');
62
- }
63
- if (isTruthy(process.env.OTTO_DEBUG_TIMING)) flags.add('timing');
64
- if (!flags.size && sawValue) flags.add('all');
65
- return { flags };
66
- }
67
-
68
- function getDebugConfig(): DebugConfig {
69
- if (!cachedConfig) cachedConfig = parseDebugConfig();
70
- return cachedConfig;
71
- }
8
+ import { time as timeNew } from '@ottocode/sdk';
72
9
 
73
10
  /**
74
11
  * Check if debug mode is enabled for a specific flag
@@ -77,33 +14,8 @@ function getDebugConfig(): DebugConfig {
77
14
  * @deprecated Use isDebugEnabled from debug-state.ts instead
78
15
  */
79
16
  export function isDebugEnabled(flag?: string): boolean {
80
- // Use new centralized debug state for general debug
81
- if (!flag || flag === 'log') {
82
- return isDebugEnabledNew();
83
- }
84
-
85
- // For specific flags like 'timing', check both new state and legacy env vars
86
- if (flag === 'timing') {
87
- // If new debug state is enabled OR timing flag is set
88
- if (isDebugEnabledNew()) return true;
89
- }
90
-
91
- // Legacy flag checking
92
- const config = getDebugConfig();
93
- if (config.flags.has('all')) return true;
94
- if (flag) return config.flags.has(flag);
95
- return config.flags.has('log');
96
- }
97
-
98
- /**
99
- * Log debug message
100
- * Now uses the centralized logger
101
- *
102
- * @deprecated Use logger.debug from logger.ts instead
103
- */
104
- export function debugLog(...args: unknown[]) {
105
- if (!isDebugEnabled('log')) return;
106
- debugNew(args.map((arg) => String(arg)).join(' '));
17
+ void flag;
18
+ return false;
107
19
  }
108
20
 
109
21
  /**
@@ -2,12 +2,9 @@
2
2
  * Runtime debug state management
3
3
  *
4
4
  * Centralizes debug flag state that can be set either via:
5
- * - Environment variables (OTTO_DEBUG, DEBUG_OTTO)
6
5
  * - Runtime configuration (CLI --debug flag)
7
6
  */
8
7
 
9
- const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
10
-
11
8
  type DebugState = {
12
9
  enabled: boolean;
13
10
  traceEnabled: boolean;
@@ -23,66 +20,16 @@ const state: DebugState = {
23
20
  runtimeTraceOverride: null,
24
21
  };
25
22
 
26
- type GlobalDebugFlags = {
27
- __OTTO_DEBUG_ENABLED__?: boolean;
28
- __OTTO_TRACE_ENABLED__?: boolean;
29
- };
30
-
31
- const globalFlags = globalThis as GlobalDebugFlags;
32
-
33
- function syncGlobalFlags() {
34
- globalFlags.__OTTO_DEBUG_ENABLED__ = state.enabled;
35
- globalFlags.__OTTO_TRACE_ENABLED__ = state.traceEnabled;
36
- }
37
-
38
- /**
39
- * Check if environment variables indicate debug mode
40
- */
41
- function checkEnvDebug(): boolean {
42
- const sources = [process.env.OTTO_DEBUG, process.env.DEBUG_OTTO];
43
- for (const value of sources) {
44
- if (!value) continue;
45
- const trimmed = value.trim().toLowerCase();
46
- if (TRUTHY.has(trimmed) || trimmed === 'all') {
47
- return true;
48
- }
49
- }
50
- return false;
51
- }
52
-
53
- /**
54
- * Check if environment variables indicate trace mode
55
- */
56
- function checkEnvTrace(): boolean {
57
- const sources = [process.env.OTTO_TRACE, process.env.TRACE_OTTO];
58
- for (const value of sources) {
59
- if (!value) continue;
60
- const trimmed = value.trim().toLowerCase();
61
- if (TRUTHY.has(trimmed)) {
62
- return true;
63
- }
64
- }
65
- return false;
66
- }
67
-
68
- function checkEnvDevtools(): boolean {
69
- const raw = process.env.OTTO_DEVTOOLS;
70
- if (!raw) return false;
71
- const trimmed = raw.trim().toLowerCase();
72
- return TRUTHY.has(trimmed);
73
- }
74
-
75
23
  /**
76
24
  * Initialize debug state from environment
77
25
  */
78
26
  function initialize() {
79
27
  if (state.runtimeOverride === null) {
80
- state.enabled = checkEnvDebug();
28
+ state.enabled = false;
81
29
  }
82
30
  if (state.runtimeTraceOverride === null) {
83
- state.traceEnabled = checkEnvTrace();
31
+ state.traceEnabled = false;
84
32
  }
85
- syncGlobalFlags();
86
33
  }
87
34
 
88
35
  /**
@@ -104,7 +51,7 @@ export function isTraceEnabled(): boolean {
104
51
  }
105
52
 
106
53
  export function isDevtoolsEnabled(): boolean {
107
- return checkEnvDevtools();
54
+ return false;
108
55
  }
109
56
 
110
57
  /**
@@ -116,7 +63,6 @@ export function isDevtoolsEnabled(): boolean {
116
63
  export function setDebugEnabled(enabled: boolean): void {
117
64
  state.enabled = enabled;
118
65
  state.runtimeOverride = enabled;
119
- syncGlobalFlags();
120
66
  }
121
67
 
122
68
  /**
@@ -128,7 +74,6 @@ export function setDebugEnabled(enabled: boolean): void {
128
74
  export function setTraceEnabled(enabled: boolean): void {
129
75
  state.traceEnabled = enabled;
130
76
  state.runtimeTraceOverride = enabled;
131
- syncGlobalFlags();
132
77
  }
133
78
 
134
79
  /**
@@ -137,9 +82,8 @@ export function setTraceEnabled(enabled: boolean): void {
137
82
  export function resetDebugState(): void {
138
83
  state.runtimeOverride = null;
139
84
  state.runtimeTraceOverride = null;
140
- state.enabled = checkEnvDebug();
141
- state.traceEnabled = checkEnvTrace();
142
- syncGlobalFlags();
85
+ state.enabled = false;
86
+ state.traceEnabled = false;
143
87
  }
144
88
 
145
89
  /**
@@ -3,11 +3,7 @@ import { join } from 'node:path';
3
3
  import { mkdir } from 'node:fs/promises';
4
4
  import { isDebugEnabled } from './state.ts';
5
5
 
6
- const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
7
-
8
6
  function isDumpEnabled(): boolean {
9
- const explicit = process.env.OTTO_DEBUG_DUMP;
10
- if (explicit) return TRUTHY.has(explicit.trim().toLowerCase());
11
7
  return isDebugEnabled();
12
8
  }
13
9
 
@@ -5,7 +5,6 @@ import { streamText } from 'ai';
5
5
  import { resolveModel } from '../provider/index.ts';
6
6
  import { getAuth } from '@ottocode/sdk';
7
7
  import { loadConfig } from '@ottocode/sdk';
8
- import { debugLog } from '../debug/index.ts';
9
8
  import { getModelLimits } from './compaction-limits.ts';
10
9
  import { buildCompactionContext } from './compaction-context.ts';
11
10
  import { getCompactionSystemPrompt } from './compaction-detect.ts';
@@ -29,16 +28,11 @@ export async function performAutoCompaction(
29
28
  error?: string;
30
29
  compactMessageId?: string;
31
30
  }> {
32
- debugLog(`[compaction] Starting auto-compaction for session ${sessionId}`);
33
-
34
31
  try {
35
32
  const limits = getModelLimits(provider, modelId);
36
33
  const contextTokenLimit = limits
37
34
  ? Math.max(Math.floor(limits.context * 0.5), 15000)
38
35
  : 15000;
39
- debugLog(
40
- `[compaction] Model ${modelId} context limit: ${limits?.context ?? 'unknown'}, using ${contextTokenLimit} tokens for compaction`,
41
- );
42
36
 
43
37
  const context = await buildCompactionContext(
44
38
  db,
@@ -46,14 +40,10 @@ export async function performAutoCompaction(
46
40
  contextTokenLimit,
47
41
  );
48
42
  if (!context || context.length < 100) {
49
- debugLog('[compaction] Not enough context to compact');
50
43
  return { success: false, error: 'Not enough context to compact' };
51
44
  }
52
45
 
53
46
  const cfg = await loadConfig();
54
- debugLog(
55
- `[compaction] Using session model ${provider}/${modelId} for auto-compaction`,
56
- );
57
47
 
58
48
  const auth = await getAuth(
59
49
  provider as Parameters<typeof getAuth>[0],
@@ -61,10 +51,6 @@ export async function performAutoCompaction(
61
51
  );
62
52
  const oauth = detectOAuth(provider, auth);
63
53
 
64
- debugLog(
65
- `[compaction] OAuth: needsSpoof=${oauth.needsSpoof}, isOpenAIOAuth=${oauth.isOpenAIOAuth}`,
66
- );
67
-
68
54
  const model = await resolveModel(
69
55
  provider as Parameters<typeof resolveModel>[0],
70
56
  modelId,
@@ -130,25 +116,19 @@ export async function performAutoCompaction(
130
116
  .where(eq(messageParts.id, compactPartId));
131
117
 
132
118
  if (!summary || summary.length < 50) {
133
- debugLog('[compaction] Failed to generate summary');
134
119
  return { success: false, error: 'Failed to generate summary' };
135
120
  }
136
121
 
137
- debugLog(`[compaction] Generated summary: ${summary.slice(0, 100)}...`);
138
-
139
122
  const compactResult = await markSessionCompacted(
140
123
  db,
141
124
  sessionId,
142
125
  assistantMessageId,
143
126
  );
144
- debugLog(
145
- `[compaction] Marked ${compactResult.compacted} parts as compacted, saved ~${compactResult.saved} tokens`,
146
- );
127
+ void compactResult;
147
128
 
148
129
  return { success: true, summary, compactMessageId: assistantMessageId };
149
130
  } catch (err) {
150
131
  const errorMsg = err instanceof Error ? err.message : String(err);
151
- debugLog(`[compaction] Auto-compaction failed: ${errorMsg}`);
152
132
  return { success: false, error: errorMsg };
153
133
  }
154
134
  }
@@ -1,18 +1,28 @@
1
1
  import type { getDb } from '@ottocode/database';
2
2
  import { messages, messageParts } from '@ottocode/database/schema';
3
3
  import { eq, asc, and, lt } from 'drizzle-orm';
4
- import { debugLog } from '../debug/index.ts';
5
4
  import { estimateTokens, PRUNE_PROTECT } from './compaction-limits.ts';
6
5
 
7
6
  const PROTECTED_TOOLS = ['skill'];
8
7
 
8
+ type PartInfo = {
9
+ id: string;
10
+ tokens: number;
11
+ toolCallId: string | null;
12
+ type: 'tool_call' | 'tool_result';
13
+ index: number;
14
+ };
15
+
16
+ type CompactUnit = {
17
+ partIds: string[];
18
+ tokens: number;
19
+ };
20
+
9
21
  export async function markSessionCompacted(
10
22
  db: Awaited<ReturnType<typeof getDb>>,
11
23
  sessionId: string,
12
24
  compactMessageId: string,
13
25
  ): Promise<{ compacted: number; saved: number }> {
14
- debugLog(`[compaction] Marking session ${sessionId} as compacted`);
15
-
16
26
  const compactMsg = await db
17
27
  .select()
18
28
  .from(messages)
@@ -20,7 +30,6 @@ export async function markSessionCompacted(
20
30
  .limit(1);
21
31
 
22
32
  if (!compactMsg.length) {
23
- debugLog('[compaction] Compact message not found');
24
33
  return { compacted: 0, saved: 0 };
25
34
  }
26
35
 
@@ -37,8 +46,7 @@ export async function markSessionCompacted(
37
46
  )
38
47
  .orderBy(asc(messages.createdAt));
39
48
 
40
- type PartInfo = { id: string; tokens: number };
41
- const allToolParts: PartInfo[] = [];
49
+ const allCompactUnits: CompactUnit[] = [];
42
50
  let totalToolTokens = 0;
43
51
 
44
52
  for (const msg of oldMessages) {
@@ -48,6 +56,8 @@ export async function markSessionCompacted(
48
56
  .where(eq(messageParts.messageId, msg.id))
49
57
  .orderBy(asc(messageParts.index));
50
58
 
59
+ const eligibleParts: PartInfo[] = [];
60
+
51
61
  for (const part of parts) {
52
62
  if (part.type !== 'tool_call' && part.type !== 'tool_result') continue;
53
63
  if (part.toolName && PROTECTED_TOOLS.includes(part.toolName)) continue;
@@ -69,43 +79,81 @@ export async function markSessionCompacted(
69
79
 
70
80
  const tokens = estimateTokens(contentStr);
71
81
  totalToolTokens += tokens;
72
- allToolParts.push({ id: part.id, tokens });
82
+ eligibleParts.push({
83
+ id: part.id,
84
+ tokens,
85
+ toolCallId: part.toolCallId,
86
+ type: part.type,
87
+ index: part.index,
88
+ });
89
+ }
90
+
91
+ const pairedCallIds = new Set<string>();
92
+ const callsById = new Map<string, PartInfo[]>();
93
+ const resultsById = new Map<string, PartInfo[]>();
94
+
95
+ for (const part of eligibleParts) {
96
+ if (!part.toolCallId) continue;
97
+ const bucket = part.type === 'tool_call' ? callsById : resultsById;
98
+ const items = bucket.get(part.toolCallId) ?? [];
99
+ items.push(part);
100
+ bucket.set(part.toolCallId, items);
101
+ }
102
+
103
+ for (const [toolCallId, callParts] of callsById) {
104
+ const resultParts = resultsById.get(toolCallId);
105
+ if (!resultParts?.length) continue;
106
+
107
+ const pairParts = [...callParts, ...resultParts].sort(
108
+ (a, b) => a.index - b.index,
109
+ );
110
+ pairedCallIds.add(toolCallId);
111
+ allCompactUnits.push({
112
+ partIds: pairParts.map((part) => part.id),
113
+ tokens: pairParts.reduce((sum, part) => sum + part.tokens, 0),
114
+ });
115
+ }
116
+
117
+ for (const part of eligibleParts) {
118
+ if (part.toolCallId && pairedCallIds.has(part.toolCallId)) continue;
119
+ allCompactUnits.push({
120
+ partIds: [part.id],
121
+ tokens: part.tokens,
122
+ });
73
123
  }
74
124
  }
75
125
 
76
126
  const tokensToFree = Math.max(0, totalToolTokens - PRUNE_PROTECT);
77
127
 
78
- const toCompact: PartInfo[] = [];
128
+ const toCompact: CompactUnit[] = [];
79
129
  let freedTokens = 0;
80
130
 
81
- for (const part of allToolParts) {
131
+ for (const unit of allCompactUnits) {
82
132
  if (freedTokens >= tokensToFree) break;
83
- freedTokens += part.tokens;
84
- toCompact.push(part);
133
+ freedTokens += unit.tokens;
134
+ toCompact.push(unit);
85
135
  }
86
136
 
87
- debugLog(
88
- `[compaction] Found ${toCompact.length} parts to compact (oldest first), saving ~${freedTokens} tokens`,
89
- );
90
-
91
137
  if (toCompact.length > 0) {
92
138
  const compactedAt = Date.now();
93
139
 
94
- for (const part of toCompact) {
95
- try {
96
- await db
97
- .update(messageParts)
98
- .set({ compactedAt })
99
- .where(eq(messageParts.id, part.id));
100
- } catch (err) {
101
- debugLog(
102
- `[compaction] Failed to mark part ${part.id}: ${err instanceof Error ? err.message : String(err)}`,
103
- );
140
+ for (const unit of toCompact) {
141
+ for (const partId of unit.partIds) {
142
+ try {
143
+ await db
144
+ .update(messageParts)
145
+ .set({ compactedAt })
146
+ .where(eq(messageParts.id, partId));
147
+ } catch {}
104
148
  }
105
149
  }
106
150
 
107
- debugLog(`[compaction] Marked ${toCompact.length} parts as compacted`);
151
+ const compactedParts = toCompact.reduce(
152
+ (sum, unit) => sum + unit.partIds.length,
153
+ 0,
154
+ );
155
+ return { compacted: compactedParts, saved: freedTokens };
108
156
  }
109
157
 
110
- return { compacted: toCompact.length, saved: freedTokens };
158
+ return { compacted: 0, saved: freedTokens };
111
159
  }
@@ -1,7 +1,6 @@
1
1
  import type { getDb } from '@ottocode/database';
2
2
  import { messages, messageParts } from '@ottocode/database/schema';
3
3
  import { eq, desc } from 'drizzle-orm';
4
- import { debugLog } from '../debug/index.ts';
5
4
  import { estimateTokens, PRUNE_PROTECT } from './compaction-limits.ts';
6
5
 
7
6
  const PROTECTED_TOOLS = ['skill'];
@@ -10,8 +9,6 @@ export async function pruneSession(
10
9
  db: Awaited<ReturnType<typeof getDb>>,
11
10
  sessionId: string,
12
11
  ): Promise<{ pruned: number; saved: number }> {
13
- debugLog(`[compaction] Auto-pruning session ${sessionId}`);
14
-
15
12
  const allMessages = await db
16
13
  .select()
17
14
  .from(messages)
@@ -8,7 +8,6 @@ import {
8
8
  import type { getDb } from '@ottocode/database';
9
9
  import { messages, messageParts } from '@ottocode/database/schema';
10
10
  import { eq, asc } from 'drizzle-orm';
11
- import { debugLog } from '../debug/index.ts';
12
11
  import { ToolHistoryTracker } from './tool-history-tracker.ts';
13
12
 
14
13
  /**
@@ -43,15 +42,8 @@ export async function buildHistoryMessages(
43
42
  m.status !== 'error'
44
43
  ) {
45
44
  if (parts.length === 0) {
46
- debugLog(
47
- `[buildHistoryMessages] Skipping empty assistant message ${m.id} with status ${m.status}`,
48
- );
49
45
  continue;
50
46
  }
51
-
52
- debugLog(
53
- `[buildHistoryMessages] Including non-complete assistant message ${m.id} (status: ${m.status}) with ${parts.length} parts to preserve context`,
54
- );
55
47
  }
56
48
 
57
49
  if (m.role === 'user') {
@@ -179,9 +171,6 @@ export async function buildHistoryMessages(
179
171
  let result = toolResultsById.get(obj.callId);
180
172
 
181
173
  if (!result) {
182
- debugLog(
183
- `[buildHistoryMessages] Synthesizing error result for incomplete tool call ${obj.name}#${obj.callId}`,
184
- );
185
174
  result = {
186
175
  name: obj.name,
187
176
  callId: obj.callId,
@@ -265,16 +254,6 @@ async function _logPendingToolParts(
265
254
  }
266
255
  } catch {}
267
256
  }
268
- if (pendingCalls.length) {
269
- debugLog(
270
- `[buildHistoryMessages] Pending tool calls for assistant message ${messageId}: ${pendingCalls.join(', ')}`,
271
- );
272
- }
273
- } catch (err) {
274
- debugLog(
275
- `[buildHistoryMessages] Failed to inspect pending tool calls for ${messageId}: ${
276
- err instanceof Error ? err.message : String(err)
277
- }`,
278
- );
279
- }
257
+ void pendingCalls;
258
+ } catch {}
280
259
  }