@shipfox/api-logs 10.0.0 → 10.2.0
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +23 -0
- package/dist/core/append-logs.d.ts.map +1 -1
- package/dist/core/append-logs.js +13 -5
- package/dist/core/append-logs.js.map +1 -1
- package/dist/core/close-stream.d.ts.map +1 -1
- package/dist/core/close-stream.js +16 -1
- package/dist/core/close-stream.js.map +1 -1
- package/dist/core/entities/attempt-stream.d.ts +2 -1
- package/dist/core/entities/attempt-stream.d.ts.map +1 -1
- package/dist/core/entities/attempt-stream.js.map +1 -1
- package/dist/core/session/claude/rows.d.ts +15 -3
- package/dist/core/session/claude/rows.d.ts.map +1 -1
- package/dist/core/session/claude/rows.js +221 -15
- package/dist/core/session/claude/rows.js.map +1 -1
- package/dist/core/session/claude-parser.d.ts +4 -2
- package/dist/core/session/claude-parser.d.ts.map +1 -1
- package/dist/core/session/claude-parser.js +56 -10
- package/dist/core/session/claude-parser.js.map +1 -1
- package/dist/db/db.d.ts +113 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/schema/attempt-streams.d.ts +113 -0
- package/dist/db/schema/attempt-streams.d.ts.map +1 -1
- package/dist/db/schema/attempt-streams.js +2 -0
- package/dist/db/schema/attempt-streams.js.map +1 -1
- package/dist/db/streams.d.ts +3 -1
- package/dist/db/streams.d.ts.map +1 -1
- package/dist/db/streams.js +6 -1
- package/dist/db/streams.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0002_wise_dorian_gray.sql +1 -0
- package/drizzle/meta/0002_snapshot.json +561 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +4 -4
- package/src/core/append-logs.test.ts +57 -0
- package/src/core/append-logs.ts +15 -3
- package/src/core/close-stream.ts +17 -1
- package/src/core/entities/attempt-stream.ts +2 -1
- package/src/core/session/claude/rows.ts +295 -13
- package/src/core/session/claude-parser.test.ts +620 -11
- package/src/core/session/claude-parser.ts +59 -10
- package/src/db/schema/attempt-streams.ts +3 -1
- package/src/db/streams.ts +10 -2
- package/test/fixtures/claude-session/agent-step.jsonl +12 -0
- package/test/fixtures/claude-session/sdk-system-subtypes.json +32 -0
- package/test/fixtures/claude-session.ts +45 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type {SessionViewRow} from '@shipfox/api-logs-dto';
|
|
1
|
+
import type {SessionViewRow, SessionViewToolCallRow} from '@shipfox/api-logs-dto';
|
|
2
2
|
import {z} from 'zod';
|
|
3
3
|
import {
|
|
4
4
|
assistantRows,
|
|
5
|
+
authStatusRow,
|
|
6
|
+
flushPendingToolRows,
|
|
5
7
|
PURE_PROGRESS_CLAUDE_SYSTEM_SUBTYPES,
|
|
8
|
+
rateLimitRow,
|
|
6
9
|
resultRow,
|
|
7
10
|
systemRow,
|
|
11
|
+
toolUseSummary,
|
|
8
12
|
userRows,
|
|
9
13
|
} from './claude/rows.js';
|
|
10
14
|
import {stringField} from './object.js';
|
|
@@ -21,10 +25,25 @@ export interface ClaudeParseContext {
|
|
|
21
25
|
hasInit: boolean;
|
|
22
26
|
sessionId: string | null;
|
|
23
27
|
turn: number;
|
|
28
|
+
pendingToolRows: SessionViewRow[];
|
|
29
|
+
toolCallRows: Map<string, SessionViewToolCallRow>;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
export function createClaudeParseContext(
|
|
27
|
-
|
|
32
|
+
export function createClaudeParseContext(
|
|
33
|
+
pendingToolRows: readonly SessionViewRow[] = [],
|
|
34
|
+
state: Pick<ClaudeParseContext, 'hasInit' | 'sessionId' | 'turn'> = {
|
|
35
|
+
hasInit: false,
|
|
36
|
+
sessionId: null,
|
|
37
|
+
turn: 0,
|
|
38
|
+
},
|
|
39
|
+
): ClaudeParseContext {
|
|
40
|
+
const rows = [...pendingToolRows];
|
|
41
|
+
const toolCallRows = new Map<string, SessionViewToolCallRow>();
|
|
42
|
+
for (const row of rows) {
|
|
43
|
+
if (row.kind === 'tool-call' && row.id !== null) toolCallRows.set(row.id, row);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {...state, pendingToolRows: rows, toolCallRows};
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
export function claudeInitSessionId(record: AgentSessionRecord): string | undefined {
|
|
@@ -58,11 +77,13 @@ export function parseClaudeSessionRecord(
|
|
|
58
77
|
try {
|
|
59
78
|
json = JSON.parse(record.data);
|
|
60
79
|
} catch {
|
|
61
|
-
return [rawRecordRow(record, 'Malformed session entry')];
|
|
80
|
+
return [...flushPendingToolRows(context), rawRecordRow(record, 'Malformed session entry')];
|
|
62
81
|
}
|
|
63
82
|
|
|
64
83
|
const parsed = claudeMessageSchema.safeParse(json);
|
|
65
|
-
if (!parsed.success)
|
|
84
|
+
if (!parsed.success) {
|
|
85
|
+
return [...flushPendingToolRows(context), rawRecordRow(record, 'Unsupported Claude message')];
|
|
86
|
+
}
|
|
66
87
|
|
|
67
88
|
const message = parsed.data;
|
|
68
89
|
if (
|
|
@@ -75,15 +96,43 @@ export function parseClaudeSessionRecord(
|
|
|
75
96
|
|
|
76
97
|
switch (message.type) {
|
|
77
98
|
case 'system':
|
|
99
|
+
if (stringField(message, 'subtype') === 'init') {
|
|
100
|
+
return [...flushPendingToolRows(context), systemRow(record.ts, message, context)];
|
|
101
|
+
}
|
|
102
|
+
return deferRow(context, systemRow(record.ts, message, context));
|
|
78
103
|
case 'init':
|
|
79
|
-
return [systemRow(record.ts, message, context)];
|
|
104
|
+
return [...flushPendingToolRows(context), systemRow(record.ts, message, context)];
|
|
105
|
+
case 'tool_progress':
|
|
106
|
+
case 'prompt_suggestion':
|
|
107
|
+
// These messages describe an already-emitted tool call or an interactive client state.
|
|
108
|
+
// They have no standalone row representation and must not look like parser failures.
|
|
109
|
+
return [];
|
|
110
|
+
case 'tool_use_summary':
|
|
111
|
+
return toolUseSummary(message, context) ? flushPendingToolRows(context) : [];
|
|
112
|
+
case 'auth_status':
|
|
113
|
+
return deferRow(context, authStatusRow(record.ts, message));
|
|
114
|
+
case 'rate_limit_event':
|
|
115
|
+
return deferRow(context, rateLimitRow(record.ts, message));
|
|
80
116
|
case 'assistant':
|
|
81
|
-
return assistantRows(record.ts, message);
|
|
117
|
+
return [...flushPendingToolRows(context), ...assistantRows(record.ts, message, context)];
|
|
82
118
|
case 'user':
|
|
83
|
-
return userRows(record.ts, message);
|
|
119
|
+
return userRows(record.ts, message, context);
|
|
84
120
|
case 'result':
|
|
85
|
-
return [
|
|
121
|
+
return [
|
|
122
|
+
...flushPendingToolRows(context),
|
|
123
|
+
resultRow(record.ts, message, context.turn, isFinalResult),
|
|
124
|
+
];
|
|
86
125
|
default:
|
|
87
|
-
return [
|
|
126
|
+
return [
|
|
127
|
+
...flushPendingToolRows(context),
|
|
128
|
+
rawRecordRow(record, `Unknown Claude message: ${message.type}`),
|
|
129
|
+
];
|
|
88
130
|
}
|
|
89
131
|
}
|
|
132
|
+
|
|
133
|
+
function deferRow(context: ClaudeParseContext, row: SessionViewRow): readonly SessionViewRow[] {
|
|
134
|
+
if (context.toolCallRows.size === 0) return [row];
|
|
135
|
+
|
|
136
|
+
context.pendingToolRows.push(row);
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {SessionViewLifecycleRow} from '@shipfox/api-logs-dto';
|
|
1
|
+
import type {SessionViewLifecycleRow, SessionViewRow} from '@shipfox/api-logs-dto';
|
|
2
2
|
import {uuidv7PrimaryKey} from '@shipfox/node-drizzle';
|
|
3
3
|
import {sql} from 'drizzle-orm';
|
|
4
4
|
import {
|
|
@@ -55,6 +55,7 @@ export const attemptStreams = pgTable(
|
|
|
55
55
|
claudeSessionId: text('claude_session_id'),
|
|
56
56
|
claudeTurn: integer('claude_turn').notNull().default(0),
|
|
57
57
|
claudePendingResult: jsonb('claude_pending_result').$type<SessionViewLifecycleRow>(),
|
|
58
|
+
claudePendingToolRows: jsonb('claude_pending_tool_rows').$type<SessionViewRow[]>(),
|
|
58
59
|
truncated: boolean('truncated').notNull().default(false),
|
|
59
60
|
objectKey: text('object_key'),
|
|
60
61
|
createdAt: timestamp('created_at', {withTimezone: true}).notNull().defaultNow(),
|
|
@@ -107,6 +108,7 @@ export function toAttemptStream(row: AttemptStreamDb): AttemptStream {
|
|
|
107
108
|
claudeSessionId: row.claudeSessionId,
|
|
108
109
|
claudeTurn: row.claudeTurn,
|
|
109
110
|
claudePendingResult: row.claudePendingResult ?? null,
|
|
111
|
+
claudePendingToolRows: row.claudePendingToolRows ?? [],
|
|
110
112
|
truncated: row.truncated,
|
|
111
113
|
objectKey: row.objectKey,
|
|
112
114
|
createdAt: row.createdAt,
|
package/src/db/streams.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {SessionViewLifecycleRow} from '@shipfox/api-logs-dto';
|
|
1
|
+
import type {SessionViewLifecycleRow, SessionViewRow} from '@shipfox/api-logs-dto';
|
|
2
2
|
import {and, asc, eq, getTableColumns, isNull, lt, notInArray, sql} from 'drizzle-orm';
|
|
3
3
|
import type {AttemptStream, StreamCloseReason} from '#core/entities/attempt-stream.js';
|
|
4
4
|
import {LeaseStreamMismatchError} from '#core/errors.js';
|
|
@@ -169,6 +169,7 @@ export async function setClaudeParseContext(
|
|
|
169
169
|
sessionId: string | null;
|
|
170
170
|
turn: number;
|
|
171
171
|
pendingResult: SessionViewLifecycleRow | null;
|
|
172
|
+
pendingToolRows: readonly SessionViewRow[];
|
|
172
173
|
},
|
|
173
174
|
): Promise<void> {
|
|
174
175
|
await tx
|
|
@@ -178,6 +179,7 @@ export async function setClaudeParseContext(
|
|
|
178
179
|
claudeSessionId: params.sessionId,
|
|
179
180
|
claudeTurn: params.turn,
|
|
180
181
|
claudePendingResult: params.pendingResult,
|
|
182
|
+
claudePendingToolRows: [...params.pendingToolRows],
|
|
181
183
|
updatedAt: sql`now()`,
|
|
182
184
|
})
|
|
183
185
|
.where(eq(attemptStreams.id, params.streamId));
|
|
@@ -193,7 +195,11 @@ export async function setClaudeParseContext(
|
|
|
193
195
|
export async function markStreamClosed(
|
|
194
196
|
tx: Transaction,
|
|
195
197
|
params: {streamId: string; reason: StreamCloseReason; markTruncated: boolean},
|
|
196
|
-
): Promise<{
|
|
198
|
+
): Promise<{
|
|
199
|
+
stream: AttemptStream;
|
|
200
|
+
pendingClaudeResult: SessionViewLifecycleRow | null;
|
|
201
|
+
pendingClaudeToolRows: SessionViewRow[];
|
|
202
|
+
} | null> {
|
|
197
203
|
const [open] = await tx
|
|
198
204
|
.select()
|
|
199
205
|
.from(attemptStreams)
|
|
@@ -209,6 +215,7 @@ export async function markStreamClosed(
|
|
|
209
215
|
closedAt: sql`now()`,
|
|
210
216
|
updatedAt: sql`now()`,
|
|
211
217
|
claudePendingResult: null,
|
|
218
|
+
claudePendingToolRows: null,
|
|
212
219
|
...(params.markTruncated ? {truncated: true} : {}),
|
|
213
220
|
})
|
|
214
221
|
.where(and(eq(attemptStreams.id, params.streamId), eq(attemptStreams.state, 'open')))
|
|
@@ -218,6 +225,7 @@ export async function markStreamClosed(
|
|
|
218
225
|
? {
|
|
219
226
|
stream: toAttemptStream(row),
|
|
220
227
|
pendingClaudeResult: open.claudePendingResult ?? null,
|
|
228
|
+
pendingClaudeToolRows: open.claudePendingToolRows ?? [],
|
|
221
229
|
}
|
|
222
230
|
: null;
|
|
223
231
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{"type":"system","subtype":"init","apiKeySource":"user","claude_code_version":"1.0.0","cwd":"/workspace","tools":["Read","Bash"],"mcp_servers":[],"model":"claude-sonnet-4-20250514","permissionMode":"default","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"uuid":"init-1","session_id":"session-1"}
|
|
2
|
+
{"type":"assistant","message":{"id":"msg-1","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"I will inspect the repository."},{"type":"tool_use","id":"toolu-1","name":"Read","input":{"file_path":"README.md"}}],"stop_reason":"tool_use","stop_sequence":null,"usage":{"input_tokens":100,"output_tokens":20}},"parent_tool_use_id":null,"uuid":"assistant-1","session_id":"session-1"}
|
|
3
|
+
{"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"toolu-1","content":[{"type":"text","text":"# Shipfox\n"}],"is_error":false}]},"parent_tool_use_id":null,"uuid":"user-1","session_id":"session-1"}
|
|
4
|
+
{"type":"system","subtype":"permission_denied","tool_name":"mcp__shipfox_outputs__set_output","tool_use_id":"toolu-2","decision_reason_type":"mode","decision_reason":"Permission mode","message":"Permission denied","uuid":"permission-1","session_id":"session-1"}
|
|
5
|
+
{"type":"system","subtype":"api_retry","attempt":2,"max_retries":3,"retry_delay_ms":1500,"error_status":429,"error":"rate_limit","uuid":"retry-1","session_id":"session-1"}
|
|
6
|
+
{"type":"system","subtype":"hook_response","hook_id":"hook-1","hook_name":"PreToolUse","hook_event":"PreToolUse","output":"Hook completed","stdout":"","stderr":"","exit_code":0,"outcome":"success","uuid":"hook-1","session_id":"session-1"}
|
|
7
|
+
{"type":"system","subtype":"compact_boundary","compact_metadata":{"trigger":"auto","pre_tokens":12000,"post_tokens":4000,"duration_ms":250},"uuid":"compact-1","session_id":"session-1"}
|
|
8
|
+
{"type":"result","subtype":"success","is_error":false,"result":"First turn response","duration_ms":1200,"duration_api_ms":900,"num_turns":1,"total_cost_usd":0.0042,"uuid":"result-1","session_id":"session-1"}
|
|
9
|
+
{"type":"system","subtype":"init","apiKeySource":"user","claude_code_version":"1.0.0","cwd":"/workspace","tools":["Read","Bash"],"mcp_servers":[],"model":"claude-sonnet-4-20250514","permissionMode":"default","slash_commands":[],"output_style":"default","skills":[],"plugins":[],"uuid":"init-2","session_id":"session-1"}
|
|
10
|
+
{"type":"user","message":{"role":"user","content":"The previous turn ended without setting required workflow outputs: answer. Call set_output for each missing key, then provide your final response."},"parent_tool_use_id":null,"isSynthetic":true,"uuid":"user-2","session_id":"session-1"}
|
|
11
|
+
{"type":"assistant","message":{"id":"msg-2","type":"message","role":"assistant","model":"claude-sonnet-4-20250514","content":[{"type":"text","text":"The answer is ready."}],"stop_reason":"end_turn","stop_sequence":null,"usage":{"input_tokens":120,"output_tokens":8}},"parent_tool_use_id":null,"uuid":"assistant-2","session_id":"session-1"}
|
|
12
|
+
{"type":"result","subtype":"success","is_error":false,"result":"Final response","duration_ms":800,"duration_api_ms":600,"num_turns":1,"total_cost_usd":0.0021,"uuid":"result-2","session_id":"session-1"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package": "@anthropic-ai/claude-agent-sdk",
|
|
3
|
+
"version": "0.3.200",
|
|
4
|
+
"subtypes": [
|
|
5
|
+
"api_retry",
|
|
6
|
+
"commands_changed",
|
|
7
|
+
"compact_boundary",
|
|
8
|
+
"elicitation_complete",
|
|
9
|
+
"files_persisted",
|
|
10
|
+
"hook_progress",
|
|
11
|
+
"hook_response",
|
|
12
|
+
"hook_started",
|
|
13
|
+
"informational",
|
|
14
|
+
"init",
|
|
15
|
+
"local_command_output",
|
|
16
|
+
"memory_recall",
|
|
17
|
+
"mirror_error",
|
|
18
|
+
"model_refusal_fallback",
|
|
19
|
+
"model_refusal_no_fallback",
|
|
20
|
+
"notification",
|
|
21
|
+
"permission_denied",
|
|
22
|
+
"plugin_install",
|
|
23
|
+
"session_state_changed",
|
|
24
|
+
"status",
|
|
25
|
+
"task_notification",
|
|
26
|
+
"task_progress",
|
|
27
|
+
"task_started",
|
|
28
|
+
"task_updated",
|
|
29
|
+
"thinking_tokens",
|
|
30
|
+
"worker_shutting_down"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {readFileSync} from 'node:fs';
|
|
2
|
+
import type {AgentSessionRecord} from '#core/session/session-record.js';
|
|
3
|
+
|
|
4
|
+
export interface ClaudeSystemSubtypeFixture {
|
|
5
|
+
package: string;
|
|
6
|
+
version: string;
|
|
7
|
+
subtypes: string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const SDK_ENTRY_URL = import.meta.resolve('@anthropic-ai/claude-agent-sdk');
|
|
11
|
+
const SDK_TYPES_URL = new URL('./sdk.d.ts', SDK_ENTRY_URL);
|
|
12
|
+
|
|
13
|
+
export function readClaudeSessionFixture(name: string): AgentSessionRecord[] {
|
|
14
|
+
const contents = readFileSync(
|
|
15
|
+
new URL(`./claude-session/${name}`, import.meta.url),
|
|
16
|
+
'utf8',
|
|
17
|
+
).trim();
|
|
18
|
+
|
|
19
|
+
return contents.split('\n').map((data, index) => ({data, ts: index + 1}));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function readClaudeSystemSubtypeFixture(): ClaudeSystemSubtypeFixture {
|
|
23
|
+
const contents = readFileSync(
|
|
24
|
+
new URL('./claude-session/sdk-system-subtypes.json', import.meta.url),
|
|
25
|
+
'utf8',
|
|
26
|
+
);
|
|
27
|
+
return JSON.parse(contents) as ClaudeSystemSubtypeFixture;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function readInstalledClaudeSdkVersion(): string {
|
|
31
|
+
const contents = readFileSync(new URL('./package.json', SDK_TYPES_URL), 'utf8');
|
|
32
|
+
const packageJson = JSON.parse(contents) as {version?: unknown};
|
|
33
|
+
if (typeof packageJson.version !== 'string') {
|
|
34
|
+
throw new Error('The installed Claude SDK package has no version');
|
|
35
|
+
}
|
|
36
|
+
return packageJson.version;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function readInstalledClaudeSystemSubtypes(): string[] {
|
|
40
|
+
const declaration = readFileSync(SDK_TYPES_URL, 'utf8');
|
|
41
|
+
return [...declaration.matchAll(/type: 'system';\s+subtype: '([^']+)'/g)]
|
|
42
|
+
.map((match) => match[1])
|
|
43
|
+
.filter((subtype): subtype is string => subtype !== undefined)
|
|
44
|
+
.sort();
|
|
45
|
+
}
|