@loom-framework/core 0.1.0-alpha.6 → 0.1.0-alpha.61
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/dist/adapter-base.d.ts +29 -0
- package/dist/adapter-base.d.ts.map +1 -0
- package/dist/adapter-base.js +62 -0
- package/dist/adapter-base.js.map +1 -0
- package/dist/adapter-factory.d.ts +8 -0
- package/dist/adapter-factory.d.ts.map +1 -0
- package/dist/adapter-factory.js +25 -0
- package/dist/adapter-factory.js.map +1 -0
- package/dist/adapter-filesystem.d.ts +6 -11
- package/dist/adapter-filesystem.d.ts.map +1 -1
- package/dist/adapter-filesystem.js +17 -38
- package/dist/adapter-filesystem.js.map +1 -1
- package/dist/adapter-sqlite.d.ts +6 -23
- package/dist/adapter-sqlite.d.ts.map +1 -1
- package/dist/adapter-sqlite.js +45 -50
- package/dist/adapter-sqlite.js.map +1 -1
- package/dist/backend/ai/button-resolver.d.ts +18 -0
- package/dist/backend/ai/button-resolver.d.ts.map +1 -0
- package/dist/backend/ai/button-resolver.js +58 -0
- package/dist/backend/ai/button-resolver.js.map +1 -0
- package/dist/backend/ai/engine.d.ts +52 -0
- package/dist/backend/ai/engine.d.ts.map +1 -0
- package/dist/backend/ai/engine.js +189 -0
- package/dist/backend/ai/engine.js.map +1 -0
- package/dist/backend/ai/index.d.ts +11 -0
- package/dist/backend/ai/index.d.ts.map +1 -0
- package/dist/backend/ai/index.js +8 -0
- package/dist/backend/ai/index.js.map +1 -0
- package/dist/backend/ai/output-parser.d.ts +29 -0
- package/dist/backend/ai/output-parser.d.ts.map +1 -0
- package/dist/backend/ai/output-parser.js +247 -0
- package/dist/backend/ai/output-parser.js.map +1 -0
- package/dist/backend/ai/session-manager.d.ts +103 -0
- package/dist/backend/ai/session-manager.d.ts.map +1 -0
- package/dist/backend/ai/session-manager.js +298 -0
- package/dist/backend/ai/session-manager.js.map +1 -0
- package/dist/backend/index.d.ts +61 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +160 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/observe/index.d.ts +6 -0
- package/dist/backend/observe/index.d.ts.map +1 -0
- package/dist/backend/observe/index.js +5 -0
- package/dist/backend/observe/index.js.map +1 -0
- package/dist/backend/observe/logger.d.ts +28 -0
- package/dist/backend/observe/logger.d.ts.map +1 -0
- package/dist/backend/observe/logger.js +80 -0
- package/dist/backend/observe/logger.js.map +1 -0
- package/dist/backend/observe/types.d.ts +26 -0
- package/dist/backend/observe/types.d.ts.map +1 -0
- package/dist/backend/observe/types.js +7 -0
- package/dist/backend/observe/types.js.map +1 -0
- package/dist/backend/routes/chat.d.ts +31 -0
- package/dist/backend/routes/chat.d.ts.map +1 -0
- package/dist/backend/routes/chat.js +426 -0
- package/dist/backend/routes/chat.js.map +1 -0
- package/dist/backend/routes/data.d.ts +13 -0
- package/dist/backend/routes/data.d.ts.map +1 -0
- package/dist/backend/routes/data.js +129 -0
- package/dist/backend/routes/data.js.map +1 -0
- package/dist/backend/routes/health.d.ts +7 -0
- package/dist/backend/routes/health.d.ts.map +1 -0
- package/dist/backend/routes/health.js +15 -0
- package/dist/backend/routes/health.js.map +1 -0
- package/dist/backend/routes/index.d.ts +9 -0
- package/dist/backend/routes/index.d.ts.map +1 -0
- package/dist/backend/routes/index.js +8 -0
- package/dist/backend/routes/index.js.map +1 -0
- package/dist/backend/routes/upload.d.ts +24 -0
- package/dist/backend/routes/upload.d.ts.map +1 -0
- package/dist/backend/routes/upload.js +67 -0
- package/dist/backend/routes/upload.js.map +1 -0
- package/dist/bin.d.ts +8 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +12 -0
- package/dist/bin.js.map +1 -0
- package/dist/capability-generator.d.ts +16 -6
- package/dist/capability-generator.d.ts.map +1 -1
- package/dist/capability-generator.js +160 -248
- package/dist/capability-generator.js.map +1 -1
- package/dist/cli/commands/build.d.ts +11 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +170 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/data.d.ts +12 -0
- package/dist/cli/commands/data.d.ts.map +1 -0
- package/dist/cli/commands/data.js +158 -0
- package/dist/cli/commands/data.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +9 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +114 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/generate-capabilities.d.ts +8 -0
- package/dist/cli/commands/generate-capabilities.d.ts.map +1 -0
- package/dist/cli/commands/generate-capabilities.js +40 -0
- package/dist/cli/commands/generate-capabilities.js.map +1 -0
- package/dist/cli/commands/generate-cli-command.d.ts +8 -0
- package/dist/cli/commands/generate-cli-command.d.ts.map +1 -0
- package/dist/cli/commands/generate-cli-command.js +64 -0
- package/dist/cli/commands/generate-cli-command.js.map +1 -0
- package/dist/cli/commands/generate-page.d.ts +9 -0
- package/dist/cli/commands/generate-page.d.ts.map +1 -0
- package/dist/cli/commands/generate-page.js +418 -0
- package/dist/cli/commands/generate-page.js.map +1 -0
- package/dist/cli/commands/generate-skill.d.ts +8 -0
- package/dist/cli/commands/generate-skill.d.ts.map +1 -0
- package/dist/cli/commands/generate-skill.js +75 -0
- package/dist/cli/commands/generate-skill.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +6 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +17 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +539 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/observe.d.ts +9 -0
- package/dist/cli/commands/observe.d.ts.map +1 -0
- package/dist/cli/commands/observe.js +142 -0
- package/dist/cli/commands/observe.js.map +1 -0
- package/dist/cli/commands/skill.d.ts +9 -0
- package/dist/cli/commands/skill.d.ts.map +1 -0
- package/dist/cli/commands/skill.js +186 -0
- package/dist/cli/commands/skill.js.map +1 -0
- package/dist/cli/helpers/duration.d.ts +5 -0
- package/dist/cli/helpers/duration.d.ts.map +1 -0
- package/dist/cli/helpers/duration.js +19 -0
- package/dist/cli/helpers/duration.js.map +1 -0
- package/dist/cli/helpers/field-template.d.ts +9 -0
- package/dist/cli/helpers/field-template.d.ts.map +1 -0
- package/dist/cli/helpers/field-template.js +92 -0
- package/dist/cli/helpers/field-template.js.map +1 -0
- package/dist/cli/helpers/naming.d.ts +12 -0
- package/dist/cli/helpers/naming.d.ts.map +1 -0
- package/dist/cli/helpers/naming.js +25 -0
- package/dist/cli/helpers/naming.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +33 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils.d.ts +10 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +31 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/config.d.ts +8 -33
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -7
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/server-bin.d.ts +12 -0
- package/dist/server-bin.d.ts.map +1 -0
- package/dist/server-bin.js +75 -0
- package/dist/server-bin.js.map +1 -0
- package/dist/types.d.ts +28 -18
- package/dist/types.d.ts.map +1 -1
- package/package.json +17 -4
- package/templates/skill/SKILL.md +140 -0
- package/templates/skill/references/README.md +128 -0
- package/templates/skill/references/data-model.md +78 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Resolver - Resolve AI button prompts from configuration
|
|
3
|
+
*
|
|
4
|
+
* Replaces {{var}} placeholders in prompt with context values.
|
|
5
|
+
* Context variables not referenced in the template are appended to the prompt.
|
|
6
|
+
*/
|
|
7
|
+
import type { LoomConfig } from '../../index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Resolve prompt from AI button configuration.
|
|
10
|
+
* - Replaces {{var}} in prompt with context values
|
|
11
|
+
* - Appends unreferenced context fields to the end
|
|
12
|
+
* Returns null if button not found.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveButtonPrompt(buttonId: string, context: Record<string, string> | undefined, config: LoomConfig): {
|
|
15
|
+
prompt: string;
|
|
16
|
+
error?: string;
|
|
17
|
+
} | null;
|
|
18
|
+
//# sourceMappingURL=button-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/ai/button-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAmBjD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,MAAM,EAAE,UAAU,GACjB;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAoC3C"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button Resolver - Resolve AI button prompts from configuration
|
|
3
|
+
*
|
|
4
|
+
* Replaces {{var}} placeholders in prompt with context values.
|
|
5
|
+
* Context variables not referenced in the template are appended to the prompt.
|
|
6
|
+
*/
|
|
7
|
+
const TEMPLATE_VAR = /\{\{(\w+)\}\}/g;
|
|
8
|
+
/**
|
|
9
|
+
* Extract variable names from a prompt template string.
|
|
10
|
+
*/
|
|
11
|
+
function extractVarNames(prompt) {
|
|
12
|
+
const names = [];
|
|
13
|
+
let match;
|
|
14
|
+
const re = new RegExp(TEMPLATE_VAR.source, 'g');
|
|
15
|
+
while ((match = re.exec(prompt)) !== null) {
|
|
16
|
+
if (!names.includes(match[1])) {
|
|
17
|
+
names.push(match[1]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return names;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolve prompt from AI button configuration.
|
|
24
|
+
* - Replaces {{var}} in prompt with context values
|
|
25
|
+
* - Appends unreferenced context fields to the end
|
|
26
|
+
* Returns null if button not found.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveButtonPrompt(buttonId, context, config) {
|
|
29
|
+
const button = config.aiButtons?.find((b) => b.id === buttonId);
|
|
30
|
+
if (!button) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const varNames = extractVarNames(button.prompt);
|
|
34
|
+
// Check that all referenced variables have values in context
|
|
35
|
+
if (varNames.length > 0) {
|
|
36
|
+
const missing = varNames.filter((v) => !context || context[v] === undefined);
|
|
37
|
+
if (missing.length > 0) {
|
|
38
|
+
return {
|
|
39
|
+
prompt: '',
|
|
40
|
+
error: `Missing context variables: ${missing.join(', ')}. Required: ${varNames.join(', ')}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Replace {{var}} with context values
|
|
45
|
+
let resolved = button.prompt;
|
|
46
|
+
if (context) {
|
|
47
|
+
resolved = resolved.replace(TEMPLATE_VAR, (match, key) => {
|
|
48
|
+
return context[key] !== undefined ? String(context[key]) : match;
|
|
49
|
+
});
|
|
50
|
+
// Append unreferenced context fields
|
|
51
|
+
const unreferenced = Object.entries(context).filter(([key]) => !varNames.includes(key));
|
|
52
|
+
if (unreferenced.length > 0) {
|
|
53
|
+
resolved += '\n\n' + unreferenced.map(([key, value]) => `${key}: ${value}`).join('\n');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { prompt: resolved };
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=button-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button-resolver.js","sourceRoot":"","sources":["../../../src/backend/ai/button-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAEtC;;GAEG;AACH,SAAS,eAAe,CAAC,MAAc;IACrC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAA6B,CAAC;IAClC,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChD,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,OAA2C,EAC3C,MAAkB;IAElB,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC;IAChE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEhD,6DAA6D;IAC7D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;QAC7E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,MAAM,EAAE,EAAE;gBACV,KAAK,EAAE,8BAA8B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAC5F,CAAC;QACJ,CAAC;IACH,CAAC;IAED,sCAAsC;IACtC,IAAI,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC;IAC7B,IAAI,OAAO,EAAE,CAAC;QACZ,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,GAAW,EAAE,EAAE;YAC/D,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QACnE,CAAC,CAAC,CAAC;QAEH,qCAAqC;QACrC,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CACjD,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CACnC,CAAC;QACF,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,QAAQ,IAAI,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Engine - Core abstraction for AI communication
|
|
3
|
+
*
|
|
4
|
+
* ClaudeCodeEngine: spawns `claude -p --output-format stream-json` processes
|
|
5
|
+
* with support for --resume, --model, and auto-discovered project skills.
|
|
6
|
+
*/
|
|
7
|
+
import type { AIEngine, AICallOptions, AIChunk, ClaudeCodeConfig } from '../../index.js';
|
|
8
|
+
/** Log levels for engine diagnostics */
|
|
9
|
+
export type EngineLogLevel = 'COMMAND' | 'PROMPT' | 'STDOUT_CHUNK' | 'RESPONSE_PARSED' | 'STDERR' | 'PROCESS_EXIT';
|
|
10
|
+
export interface EngineLogEntry {
|
|
11
|
+
level: EngineLogLevel;
|
|
12
|
+
sessionId: string;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
message: string;
|
|
15
|
+
data?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export type EngineLogger = (entry: EngineLogEntry) => void;
|
|
18
|
+
export interface ClaudeCodeEngineOptions {
|
|
19
|
+
config: ClaudeCodeConfig;
|
|
20
|
+
projectRoot: string;
|
|
21
|
+
/** Optional diagnostic logger for engine operations */
|
|
22
|
+
logger?: EngineLogger;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* ClaudeCodeEngine - Spawns Claude Code in headless mode
|
|
26
|
+
*
|
|
27
|
+
* Uses `claude -p --output-format stream-json` to invoke AI capabilities.
|
|
28
|
+
* Project skills in `.claude/skills/` are auto-discovered by Claude Code.
|
|
29
|
+
* Supports session resume and model selection.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ClaudeCodeEngine implements AIEngine {
|
|
32
|
+
readonly name = "claude-code";
|
|
33
|
+
private config;
|
|
34
|
+
private projectRoot;
|
|
35
|
+
private activeProcesses;
|
|
36
|
+
private logger?;
|
|
37
|
+
constructor(options: ClaudeCodeEngineOptions);
|
|
38
|
+
private log;
|
|
39
|
+
/**
|
|
40
|
+
* Call Claude Code with a prompt and stream back AIChunk objects
|
|
41
|
+
*/
|
|
42
|
+
call(prompt: string, options: AICallOptions): AsyncGenerator<AIChunk>;
|
|
43
|
+
/**
|
|
44
|
+
* Kill an active process by session ID
|
|
45
|
+
*/
|
|
46
|
+
killSession(sessionId: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Kill all active processes (for graceful shutdown)
|
|
49
|
+
*/
|
|
50
|
+
killAll(): void;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/backend/ai/engine.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAMzF,wCAAwC;AACxC,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,cAAc,GAAG,iBAAiB,GAAG,QAAQ,GAAG,cAAc,CAAC;AAEnH,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,QAAQ;IAC/C,QAAQ,CAAC,IAAI,iBAAiB;IAE9B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,MAAM,CAAC,CAAe;gBAElB,OAAO,EAAE,uBAAuB;IAM5C,OAAO,CAAC,GAAG;IAUX;;OAEG;IACI,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,cAAc,CAAC,OAAO,CAAC;IAmI5E;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAUvC;;OAEG;IACH,OAAO,IAAI,IAAI;CAQhB"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Engine - Core abstraction for AI communication
|
|
3
|
+
*
|
|
4
|
+
* ClaudeCodeEngine: spawns `claude -p --output-format stream-json` processes
|
|
5
|
+
* with support for --resume, --model, and auto-discovered project skills.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { parseClaudeOutput } from './output-parser.js';
|
|
10
|
+
/** Default timeout for Claude Code processes (2 minutes) */
|
|
11
|
+
const DEFAULT_PROCESS_TIMEOUT_MS = 120_000;
|
|
12
|
+
/**
|
|
13
|
+
* ClaudeCodeEngine - Spawns Claude Code in headless mode
|
|
14
|
+
*
|
|
15
|
+
* Uses `claude -p --output-format stream-json` to invoke AI capabilities.
|
|
16
|
+
* Project skills in `.claude/skills/` are auto-discovered by Claude Code.
|
|
17
|
+
* Supports session resume and model selection.
|
|
18
|
+
*/
|
|
19
|
+
export class ClaudeCodeEngine {
|
|
20
|
+
name = 'claude-code';
|
|
21
|
+
config;
|
|
22
|
+
projectRoot;
|
|
23
|
+
activeProcesses = new Map();
|
|
24
|
+
logger;
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.config = options.config;
|
|
27
|
+
this.projectRoot = options.projectRoot;
|
|
28
|
+
this.logger = options.logger;
|
|
29
|
+
}
|
|
30
|
+
log(level, sessionId, message, data) {
|
|
31
|
+
this.logger?.({
|
|
32
|
+
level,
|
|
33
|
+
sessionId,
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
message,
|
|
36
|
+
data,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Call Claude Code with a prompt and stream back AIChunk objects
|
|
41
|
+
*/
|
|
42
|
+
async *call(prompt, options) {
|
|
43
|
+
const claudePath = this.config.path || process.env.CLAUDE_CODE_PATH || 'claude';
|
|
44
|
+
const timeout = options.timeout ?? this.config.timeout ?? DEFAULT_PROCESS_TIMEOUT_MS;
|
|
45
|
+
const pluginRoot = this.config.pluginRoot || path.join(this.projectRoot, '.claude');
|
|
46
|
+
// Build prompt with file references if provided
|
|
47
|
+
const fullPrompt = buildPromptWithFiles(prompt, options.files);
|
|
48
|
+
const args = [
|
|
49
|
+
'-p',
|
|
50
|
+
'--output-format', 'stream-json',
|
|
51
|
+
'--verbose',
|
|
52
|
+
'--include-partial-messages',
|
|
53
|
+
];
|
|
54
|
+
if (this.config.skipPermissions !== false) {
|
|
55
|
+
args.push('--dangerously-skip-permissions');
|
|
56
|
+
}
|
|
57
|
+
if (options.model || this.config.defaultModel) {
|
|
58
|
+
args.push('--model', options.model || this.config.defaultModel);
|
|
59
|
+
}
|
|
60
|
+
if (options.resumeSessionId) {
|
|
61
|
+
args.push('--resume', options.resumeSessionId);
|
|
62
|
+
}
|
|
63
|
+
// Only load project-level settings in headless mode
|
|
64
|
+
// (skip user/local layers to avoid personal plugins interfering)
|
|
65
|
+
args.push('--setting-sources', 'project');
|
|
66
|
+
// Read prompt from stdin to avoid ARG_MAX
|
|
67
|
+
args.push('-');
|
|
68
|
+
this.log('COMMAND', options.sessionId, `Spawning claude process`, { claudePath, args });
|
|
69
|
+
this.log('PROMPT', options.sessionId, `Prompt length: ${fullPrompt.length}`, {
|
|
70
|
+
promptPreview: fullPrompt.slice(0, 200),
|
|
71
|
+
filesCount: options.files?.length ?? 0,
|
|
72
|
+
});
|
|
73
|
+
const childProcess = spawn(claudePath, args, {
|
|
74
|
+
cwd: this.projectRoot,
|
|
75
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
76
|
+
env: {
|
|
77
|
+
...process.env,
|
|
78
|
+
CLAUDE_PLUGIN_ROOT: pluginRoot,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
// Track active process for cleanup
|
|
82
|
+
this.activeProcesses.set(options.sessionId, childProcess);
|
|
83
|
+
// Capture stderr for error reporting
|
|
84
|
+
let stderrOutput = '';
|
|
85
|
+
childProcess.stderr.on('data', (data) => {
|
|
86
|
+
const text = data.toString();
|
|
87
|
+
stderrOutput += text;
|
|
88
|
+
this.log('STDERR', options.sessionId, text.trim());
|
|
89
|
+
});
|
|
90
|
+
// Wait for process completion with timeout
|
|
91
|
+
let timeoutHandle;
|
|
92
|
+
const completionPromise = new Promise((resolve, reject) => {
|
|
93
|
+
timeoutHandle = setTimeout(() => {
|
|
94
|
+
childProcess.kill('SIGTERM');
|
|
95
|
+
setTimeout(() => childProcess.kill('SIGKILL'), 5_000);
|
|
96
|
+
reject(new Error(`Claude Code process timed out after ${timeout}ms`));
|
|
97
|
+
}, timeout);
|
|
98
|
+
childProcess.on('close', (code) => {
|
|
99
|
+
if (timeoutHandle)
|
|
100
|
+
clearTimeout(timeoutHandle);
|
|
101
|
+
this.activeProcesses.delete(options.sessionId);
|
|
102
|
+
if (code !== null && code !== 0) {
|
|
103
|
+
this.log('PROCESS_EXIT', options.sessionId, `Process exited with code ${code}`, { exitCode: code, stderr: stderrOutput });
|
|
104
|
+
}
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
107
|
+
childProcess.on('error', (err) => {
|
|
108
|
+
if (timeoutHandle)
|
|
109
|
+
clearTimeout(timeoutHandle);
|
|
110
|
+
this.activeProcesses.delete(options.sessionId);
|
|
111
|
+
reject(err);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// Send prompt via stdin then close
|
|
115
|
+
childProcess.stdin.write(fullPrompt);
|
|
116
|
+
childProcess.stdin.end();
|
|
117
|
+
// Parse output stream
|
|
118
|
+
try {
|
|
119
|
+
const parserLogger = this.logger
|
|
120
|
+
? (entry) => {
|
|
121
|
+
this.log('STDOUT_CHUNK', options.sessionId, entry.message, entry.data);
|
|
122
|
+
}
|
|
123
|
+
: undefined;
|
|
124
|
+
for await (const chunk of parseClaudeOutput(childProcess.stdout, parserLogger)) {
|
|
125
|
+
this.log('RESPONSE_PARSED', options.sessionId, `Chunk type: ${chunk.type}`, { chunkType: chunk.type });
|
|
126
|
+
// For result-type content chunks that carry sessionId, split into two chunks
|
|
127
|
+
if (chunk.type === 'content' && chunk.sessionId) {
|
|
128
|
+
const { sessionId, usage, ...contentChunk } = chunk;
|
|
129
|
+
yield contentChunk;
|
|
130
|
+
if (sessionId) {
|
|
131
|
+
yield {
|
|
132
|
+
type: 'session_info',
|
|
133
|
+
sessionId,
|
|
134
|
+
usage,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
yield chunk;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
yield {
|
|
145
|
+
type: 'error',
|
|
146
|
+
error: error instanceof Error ? error.message : String(error),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Wait for process completion with timeout
|
|
150
|
+
await completionPromise;
|
|
151
|
+
yield { type: 'done' };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Kill an active process by session ID
|
|
155
|
+
*/
|
|
156
|
+
killSession(sessionId) {
|
|
157
|
+
const proc = this.activeProcesses.get(sessionId);
|
|
158
|
+
if (proc && !proc.killed) {
|
|
159
|
+
proc.kill('SIGTERM');
|
|
160
|
+
this.activeProcesses.delete(sessionId);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Kill all active processes (for graceful shutdown)
|
|
167
|
+
*/
|
|
168
|
+
killAll() {
|
|
169
|
+
for (const [sessionId, proc] of this.activeProcesses) {
|
|
170
|
+
if (!proc.killed) {
|
|
171
|
+
proc.kill('SIGTERM');
|
|
172
|
+
}
|
|
173
|
+
this.activeProcesses.delete(sessionId);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Build prompt with file references injected for Claude Code.
|
|
179
|
+
* Formats file paths as inline references so Claude Code can access them.
|
|
180
|
+
*/
|
|
181
|
+
function buildPromptWithFiles(prompt, files) {
|
|
182
|
+
if (!files || files.length === 0)
|
|
183
|
+
return prompt;
|
|
184
|
+
const fileList = files
|
|
185
|
+
.map((f) => ` - ${f.name}: ${f.path}`)
|
|
186
|
+
.join('\n');
|
|
187
|
+
return `${prompt}\n\nAttached files:\n${fileList}`;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/backend/ai/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Module - Barrel Export
|
|
3
|
+
*/
|
|
4
|
+
export { ClaudeCodeEngine } from './engine.js';
|
|
5
|
+
export type { ClaudeCodeEngineOptions, EngineLogger, EngineLogLevel, EngineLogEntry } from './engine.js';
|
|
6
|
+
export { SessionManager } from './session-manager.js';
|
|
7
|
+
export type { SessionRecord, SessionMessage, SessionFileRef } from './session-manager.js';
|
|
8
|
+
export { parseClaudeOutput } from './output-parser.js';
|
|
9
|
+
export type { ParserLogger, ParserLogEntry } from './output-parser.js';
|
|
10
|
+
export { resolveButtonPrompt } from './button-resolver.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backend/ai/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzG,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Module - Barrel Export
|
|
3
|
+
*/
|
|
4
|
+
export { ClaudeCodeEngine } from './engine.js';
|
|
5
|
+
export { SessionManager } from './session-manager.js';
|
|
6
|
+
export { parseClaudeOutput } from './output-parser.js';
|
|
7
|
+
export { resolveButtonPrompt } from './button-resolver.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backend/ai/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Output Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses `claude -p --output-format stream-json --verbose --include-partial-messages` output stream.
|
|
5
|
+
* Supports real-time streaming with thinking, tool calls, and text deltas.
|
|
6
|
+
*
|
|
7
|
+
* Key insight: Tool input comes via input_json_delta events, need to accumulate them.
|
|
8
|
+
*/
|
|
9
|
+
import type { AIChunk } from '../../index.js';
|
|
10
|
+
/** Parser log entry for diagnostics */
|
|
11
|
+
export interface ParserLogEntry {
|
|
12
|
+
type: 'CHUNK_RECEIVED' | 'LINE_PARSED' | 'PARSE_ERROR';
|
|
13
|
+
timestamp: string;
|
|
14
|
+
message: string;
|
|
15
|
+
data?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export type ParserLogger = (entry: ParserLogEntry) => void;
|
|
18
|
+
/**
|
|
19
|
+
* Parse Claude Code stdout stream into typed AIChunk objects
|
|
20
|
+
*
|
|
21
|
+
* Strategy:
|
|
22
|
+
* 1. Process lines as they arrive (true streaming)
|
|
23
|
+
* 2. Strip ANSI escape codes
|
|
24
|
+
* 3. Parse each line as JSON
|
|
25
|
+
* 4. Map to appropriate AIChunk types
|
|
26
|
+
* 5. Accumulate tool input from input_json_delta events
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseClaudeOutput(stdout: NodeJS.ReadableStream, logger?: ParserLogger): AsyncGenerator<AIChunk>;
|
|
29
|
+
//# sourceMappingURL=output-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output-parser.d.ts","sourceRoot":"","sources":["../../../src/backend/ai/output-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAW,MAAM,gBAAgB,CAAC;AAEvD,uCAAuC;AACvC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,gBAAgB,GAAG,aAAa,GAAG,aAAa,CAAC;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAkG3D;;;;;;;;;GASG;AACH,wBAAuB,iBAAiB,CACtC,MAAM,EAAE,MAAM,CAAC,cAAc,EAC7B,MAAM,CAAC,EAAE,YAAY,GACpB,cAAc,CAAC,OAAO,CAAC,CAiCzB"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Output Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses `claude -p --output-format stream-json --verbose --include-partial-messages` output stream.
|
|
5
|
+
* Supports real-time streaming with thinking, tool calls, and text deltas.
|
|
6
|
+
*
|
|
7
|
+
* Key insight: Tool input comes via input_json_delta events, need to accumulate them.
|
|
8
|
+
*/
|
|
9
|
+
/** ANSI escape code pattern */
|
|
10
|
+
const ANSI_ESCAPE = /\x1b\[[0-9;]*m/g;
|
|
11
|
+
/** Check if a parsed toolInput is empty (no meaningful content) */
|
|
12
|
+
function isEmptyToolInput(input) {
|
|
13
|
+
if (input === undefined || input === null)
|
|
14
|
+
return true;
|
|
15
|
+
if (typeof input === 'object' && Object.keys(input).length === 0)
|
|
16
|
+
return true;
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse Claude Code stdout stream into typed AIChunk objects
|
|
21
|
+
*
|
|
22
|
+
* Strategy:
|
|
23
|
+
* 1. Process lines as they arrive (true streaming)
|
|
24
|
+
* 2. Strip ANSI escape codes
|
|
25
|
+
* 3. Parse each line as JSON
|
|
26
|
+
* 4. Map to appropriate AIChunk types
|
|
27
|
+
* 5. Accumulate tool input from input_json_delta events
|
|
28
|
+
*/
|
|
29
|
+
export async function* parseClaudeOutput(stdout, logger) {
|
|
30
|
+
let buffer = '';
|
|
31
|
+
const state = {
|
|
32
|
+
toolInputBuffers: new Map(),
|
|
33
|
+
activeToolId: null,
|
|
34
|
+
activeToolName: null,
|
|
35
|
+
emittedToolIds: new Set(),
|
|
36
|
+
};
|
|
37
|
+
for await (const chunk of stdout) {
|
|
38
|
+
buffer += chunk.toString();
|
|
39
|
+
// Process complete lines
|
|
40
|
+
const lines = buffer.split('\n');
|
|
41
|
+
buffer = lines.pop() || '';
|
|
42
|
+
for (const line of lines) {
|
|
43
|
+
const trimmed = line.trim();
|
|
44
|
+
if (!trimmed)
|
|
45
|
+
continue;
|
|
46
|
+
const chunks = parseLine(trimmed, logger, state);
|
|
47
|
+
for (const c of chunks) {
|
|
48
|
+
yield c;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Process any remaining buffer
|
|
53
|
+
if (buffer.trim()) {
|
|
54
|
+
const cleanOutput = buffer.replace(ANSI_ESCAPE, '').trim();
|
|
55
|
+
const chunks = parseLine(cleanOutput, logger, state);
|
|
56
|
+
for (const c of chunks)
|
|
57
|
+
yield c;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function parseLine(line, logger, state) {
|
|
61
|
+
try {
|
|
62
|
+
const data = JSON.parse(line);
|
|
63
|
+
logger?.({ type: 'LINE_PARSED', timestamp: new Date().toISOString(), message: `Parsed ${data.type}` });
|
|
64
|
+
return mapToChunks(data, state);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
logger?.({ type: 'PARSE_ERROR', timestamp: new Date().toISOString(), message: 'Failed to parse line', data: line.slice(0, 200) });
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function mapToChunks(data, state) {
|
|
72
|
+
switch (data.type) {
|
|
73
|
+
case 'system':
|
|
74
|
+
if (data.subtype === 'init' && data.session_id) {
|
|
75
|
+
return [{ type: 'session_info', sessionId: data.session_id }];
|
|
76
|
+
}
|
|
77
|
+
return [];
|
|
78
|
+
case 'stream_event':
|
|
79
|
+
return handleStreamEvent(data, state);
|
|
80
|
+
case 'assistant':
|
|
81
|
+
// Process assistant message - it contains complete tool_use with full input
|
|
82
|
+
return handleAssistantMessage(data, state);
|
|
83
|
+
case 'user':
|
|
84
|
+
return handleUserMessage(data);
|
|
85
|
+
case 'result':
|
|
86
|
+
return handleResult(data);
|
|
87
|
+
default:
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function handleStreamEvent(wrapper, state) {
|
|
92
|
+
const { event } = wrapper;
|
|
93
|
+
switch (event.type) {
|
|
94
|
+
case 'content_block_delta':
|
|
95
|
+
// Handle thinking and text deltas
|
|
96
|
+
if (event.delta?.type === 'thinking_delta' && event.delta.thinking) {
|
|
97
|
+
return [{ type: 'thinking', content: event.delta.thinking }];
|
|
98
|
+
}
|
|
99
|
+
if (event.delta?.type === 'text_delta' && event.delta.text) {
|
|
100
|
+
return [{ type: 'content', content: event.delta.text }];
|
|
101
|
+
}
|
|
102
|
+
// Handle tool input JSON delta - accumulate it
|
|
103
|
+
if (event.delta?.type === 'input_json_delta' && event.delta.partial_json) {
|
|
104
|
+
if (state.activeToolId) {
|
|
105
|
+
const current = state.toolInputBuffers.get(state.activeToolId) || '';
|
|
106
|
+
state.toolInputBuffers.set(state.activeToolId, current + event.delta.partial_json);
|
|
107
|
+
}
|
|
108
|
+
return []; // Don't emit until tool call is complete
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
case 'content_block_start':
|
|
112
|
+
if (event.content_block?.type === 'tool_use') {
|
|
113
|
+
const toolUseId = event.content_block.id || '';
|
|
114
|
+
const toolName = event.content_block.name || 'unknown';
|
|
115
|
+
// Set active tool for accumulation
|
|
116
|
+
state.activeToolId = toolUseId;
|
|
117
|
+
state.activeToolName = toolName;
|
|
118
|
+
// Initialize buffer if there's already input
|
|
119
|
+
if (event.content_block.input !== undefined) {
|
|
120
|
+
state.toolInputBuffers.set(toolUseId, JSON.stringify(event.content_block.input));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
state.toolInputBuffers.set(toolUseId, '');
|
|
124
|
+
}
|
|
125
|
+
// Don't emit tool_call yet - wait for content_block_stop with complete input
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
return [];
|
|
129
|
+
case 'content_block_stop':
|
|
130
|
+
// When content block stops, emit the complete tool input if we have one
|
|
131
|
+
if (state.activeToolId && event.index !== undefined) {
|
|
132
|
+
const accumulatedInput = state.toolInputBuffers.get(state.activeToolId);
|
|
133
|
+
if (accumulatedInput && state.activeToolName) {
|
|
134
|
+
try {
|
|
135
|
+
const parsedInput = JSON.parse(accumulatedInput);
|
|
136
|
+
// Skip empty tool inputs (abandoned tool calls from Claude)
|
|
137
|
+
if (isEmptyToolInput(parsedInput)) {
|
|
138
|
+
state.activeToolId = null;
|
|
139
|
+
state.activeToolName = null;
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
const chunks = [{
|
|
143
|
+
type: 'tool_call',
|
|
144
|
+
toolUseId: state.activeToolId,
|
|
145
|
+
toolName: state.activeToolName,
|
|
146
|
+
toolInput: parsedInput,
|
|
147
|
+
}];
|
|
148
|
+
state.emittedToolIds.add(state.activeToolId);
|
|
149
|
+
state.activeToolId = null;
|
|
150
|
+
state.activeToolName = null;
|
|
151
|
+
return chunks;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// JSON not complete yet, keep state
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
state.activeToolId = null;
|
|
158
|
+
state.activeToolName = null;
|
|
159
|
+
}
|
|
160
|
+
return [];
|
|
161
|
+
default:
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function handleAssistantMessage(data, state) {
|
|
166
|
+
const chunks = [];
|
|
167
|
+
for (const block of data.message.content || []) {
|
|
168
|
+
if (block.type === 'thinking' && block.thinking) {
|
|
169
|
+
// Skip - already sent via thinking_delta
|
|
170
|
+
}
|
|
171
|
+
if (block.type === 'text' && block.text) {
|
|
172
|
+
// Skip - already sent via text_delta
|
|
173
|
+
}
|
|
174
|
+
if (block.type === 'tool_use') {
|
|
175
|
+
// Skip already-emitted tool calls (from content_block_stop)
|
|
176
|
+
const toolUseId = block.id || '';
|
|
177
|
+
if (state.emittedToolIds.has(toolUseId))
|
|
178
|
+
continue;
|
|
179
|
+
// Use the accumulated input if we have it, otherwise use the block's input
|
|
180
|
+
const accumulatedInput = state.toolInputBuffers.get(toolUseId);
|
|
181
|
+
let toolInput;
|
|
182
|
+
if (accumulatedInput) {
|
|
183
|
+
try {
|
|
184
|
+
toolInput = JSON.parse(accumulatedInput);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
toolInput = block.input;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
toolInput = block.input;
|
|
192
|
+
}
|
|
193
|
+
// Skip empty tool inputs (abandoned tool calls from Claude)
|
|
194
|
+
if (isEmptyToolInput(toolInput))
|
|
195
|
+
continue;
|
|
196
|
+
chunks.push({
|
|
197
|
+
type: 'tool_call',
|
|
198
|
+
toolUseId,
|
|
199
|
+
toolName: block.name || 'unknown',
|
|
200
|
+
toolInput,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return chunks;
|
|
205
|
+
}
|
|
206
|
+
function handleUserMessage(data) {
|
|
207
|
+
const chunks = [];
|
|
208
|
+
for (const block of data.message.content) {
|
|
209
|
+
if (block.type === 'tool_result') {
|
|
210
|
+
chunks.push({
|
|
211
|
+
type: 'tool_result',
|
|
212
|
+
toolUseId: block.tool_use_id,
|
|
213
|
+
toolResult: block.content,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return chunks;
|
|
218
|
+
}
|
|
219
|
+
function handleResult(data) {
|
|
220
|
+
const chunks = [];
|
|
221
|
+
if (data.session_id) {
|
|
222
|
+
chunks.push({
|
|
223
|
+
type: 'session_info',
|
|
224
|
+
sessionId: data.session_id,
|
|
225
|
+
usage: extractStreamUsage(data.usage),
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (data.is_error) {
|
|
229
|
+
chunks.push({ type: 'error', error: data.result || 'Unknown error' });
|
|
230
|
+
}
|
|
231
|
+
chunks.push({ type: 'done' });
|
|
232
|
+
return chunks;
|
|
233
|
+
}
|
|
234
|
+
function extractStreamUsage(usage) {
|
|
235
|
+
if (!usage)
|
|
236
|
+
return undefined;
|
|
237
|
+
const firstKey = Object.keys(usage)[0];
|
|
238
|
+
const modelUsage = firstKey ? usage[firstKey] : undefined;
|
|
239
|
+
if (!modelUsage)
|
|
240
|
+
return undefined;
|
|
241
|
+
return {
|
|
242
|
+
inputTokens: modelUsage.inputTokens,
|
|
243
|
+
outputTokens: modelUsage.outputTokens,
|
|
244
|
+
contextWindow: 0,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=output-parser.js.map
|