@alashi/provider-claude 0.3.1 → 0.5.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/dist/index.d.mts CHANGED
@@ -9,6 +9,8 @@ interface ClaudeCodeProviderOptions {
9
9
  declare class ClaudeCodeProvider implements AIProvider {
10
10
  #private;
11
11
  readonly name: string;
12
+ readonly supportsApprovals: boolean;
13
+ readonly supportsToolGate: boolean;
12
14
  constructor(options?: ClaudeCodeProviderOptions);
13
15
  createSession(options?: SessionOptions): Promise<AISession>;
14
16
  history(sessionId: string): Promise<ChatMessage[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/claude-provider.ts","../src/transcripts.ts"],"mappings":";;UAsBiB;;EAEf,kBAAkB,QAAQ;;EAE1B;;cAoGW,8BAA8B;;WAChC;EAIT,YAAY,UAAS;EAKrB,cAAoB,UAAS,iBAAsB,QAAQ;EAQ3D,QAAc,oBAAoB,QAAQ;;EAK1C,kBAAwB,uBAAuB,QAAQ;;;;;EAQvD,kBACE,aAAa,qBACb;IAAW;MACV;;;;;;;;;;;;iBCnJW,eAAe;;;;;;iBAkBT,yBACpB,+BACA,sBACC,QAAQ;;;;;;;;;;iBAwBW,yBACpB,sBAAsB,qBACtB;EAAW;EAAa;IACvB"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/claude-provider.ts","../src/transcripts.ts"],"mappings":";;UA4BiB;;EAEf,kBAAkB,QAAQ;;EAE1B;;cAwSW,8BAA8B;;WAChC;WACA;WACA;EAIT,YAAY,UAAS;EAKrB,cAAoB,UAAS,iBAAsB,QAAQ;EAQ3D,QAAc,oBAAoB,QAAQ;;EAK1C,kBAAwB,uBAAuB,QAAQ;;;;;EAQvD,kBACE,aAAa,qBACb;IAAW;MACV;;;;;;;;;;;;iBC/VW,eAAe;;;;;;iBAkBT,yBACpB,+BACA,sBACC,QAAQ;;;;;;;;;;iBAwBW,yBACpB,sBAAsB,qBACtB;EAAW;EAAa;IACvB"}
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
+ import { createHash, randomUUID } from "node:crypto";
1
2
  import { AppSdkError } from "@alashi/apps-sdk";
2
3
  import { query } from "@anthropic-ai/claude-agent-sdk";
3
4
  import { mkdir, readFile, readdir, rename, rm, stat, symlink, writeFile } from "node:fs/promises";
4
5
  import { homedir, tmpdir } from "node:os";
5
6
  import { basename, join } from "node:path";
6
- import { createHash } from "node:crypto";
7
7
  //#region src/event-mapping.ts
8
8
  /**
9
9
  * Converts the Agent SDK's per-session usage totals into per-turn deltas.
@@ -63,6 +63,16 @@ function mapMessage(message, tally) {
63
63
  sessionId: message.session_id,
64
64
  model: message.model
65
65
  }];
66
+ if (message.subtype === "compact_boundary") {
67
+ const meta = message.compact_metadata;
68
+ return [{
69
+ type: "compaction",
70
+ trigger: meta.trigger,
71
+ preTokens: meta.pre_tokens,
72
+ ...meta.post_tokens !== void 0 && { postTokens: meta.post_tokens },
73
+ ...meta.duration_ms !== void 0 && { durationMs: meta.duration_ms }
74
+ }];
75
+ }
66
76
  return [];
67
77
  case "stream_event": {
68
78
  const event = message.event;
@@ -343,8 +353,15 @@ async function importSessionTranscripts(transcripts, options) {
343
353
  }
344
354
  //#endregion
345
355
  //#region src/claude-provider.ts
346
- function toQueryOptions(options, skillsPluginDir) {
347
- const out = {
356
+ /** Engine-internal tools the gateway hook allows without consulting the host. */
357
+ const ENGINE_INTERNAL_TOOLS = /* @__PURE__ */ new Set(["ToolSearch"]);
358
+ function toQueryOptions(options, skillsPluginDir, canUseTool, gateHook) {
359
+ const out = canUseTool ? {
360
+ permissionMode: "default",
361
+ canUseTool,
362
+ settingSources: [],
363
+ includePartialMessages: true
364
+ } : {
348
365
  permissionMode: "dontAsk",
349
366
  settingSources: [],
350
367
  includePartialMessages: true
@@ -352,13 +369,14 @@ function toQueryOptions(options, skillsPluginDir) {
352
369
  if (options.systemPrompt !== void 0) out.systemPrompt = options.systemPrompt;
353
370
  if (options.model !== void 0) out.model = options.model;
354
371
  if (options.mcpServers !== void 0) out.mcpServers = options.mcpServers;
355
- if (options.allowedTools !== void 0) out.allowedTools = options.allowedTools;
372
+ if (gateHook) out.hooks = { PreToolUse: [{ hooks: [gateHook] }] };
373
+ else if (options.allowedTools !== void 0) out.allowedTools = options.allowedTools;
356
374
  if (options.maxTurns !== void 0) out.maxTurns = options.maxTurns;
357
375
  if (options.cwd !== void 0) out.cwd = options.cwd;
358
376
  if (options.resume !== void 0) out.resume = options.resume;
359
377
  if (options.agents !== void 0 && Object.keys(options.agents).length > 0) {
360
378
  out.agents = options.agents;
361
- out.allowedTools = [.../* @__PURE__ */ new Set([...out.allowedTools ?? [], "Agent"])];
379
+ if (!gateHook) out.allowedTools = [.../* @__PURE__ */ new Set([...out.allowedTools ?? [], "Agent"])];
362
380
  }
363
381
  if (skillsPluginDir !== void 0) {
364
382
  out.plugins = [{
@@ -382,10 +400,21 @@ var ClaudeSession = class {
382
400
  #busy = false;
383
401
  #closed = false;
384
402
  #tally = new UsageTally();
403
+ #askTools;
404
+ #gate;
405
+ #asked = /* @__PURE__ */ new Map();
406
+ #sessionAllowed = /* @__PURE__ */ new Set();
407
+ #pendingApprovals = /* @__PURE__ */ new Map();
408
+ #outOfBand = [];
409
+ #wakeOutOfBand;
410
+ #sdkNext;
385
411
  constructor(options, skillsPluginDir) {
412
+ this.#askTools = new Set(options.askTools ?? []);
413
+ this.#gate = options.toolGate;
414
+ const canUseTool = this.#gate || this.#askTools.size > 0 ? this.#canUseTool : void 0;
386
415
  this.#query = query({
387
416
  prompt: this.#queue,
388
- options: toQueryOptions(options, skillsPluginDir)
417
+ options: toQueryOptions(options, skillsPluginDir, canUseTool, this.#gate ? this.#gateHook : void 0)
389
418
  });
390
419
  this.#iterator = this.#query[Symbol.asyncIterator]();
391
420
  }
@@ -398,6 +427,103 @@ var ClaudeSession = class {
398
427
  this.#busy = true;
399
428
  return this.#run(input);
400
429
  }
430
+ /** PreToolUse hook: asks the host's gate about every tool call. */
431
+ #gateHook = async (hookInput) => {
432
+ if (hookInput.hook_event_name !== "PreToolUse" || !this.#gate) return {};
433
+ const decide = (permissionDecision, extra = {}) => ({ hookSpecificOutput: {
434
+ hookEventName: "PreToolUse",
435
+ permissionDecision,
436
+ ...extra
437
+ } });
438
+ if (this.#closed) return decide("deny", { permissionDecisionReason: "Session is closed" });
439
+ if (ENGINE_INTERNAL_TOOLS.has(hookInput.tool_name)) return decide("allow");
440
+ let verdict;
441
+ try {
442
+ verdict = await this.#gate({
443
+ tool: hookInput.tool_name,
444
+ input: hookInput.tool_input,
445
+ toolUseId: hookInput.tool_use_id
446
+ });
447
+ } catch (err) {
448
+ return decide("deny", { permissionDecisionReason: `Tool gateway error: ${String(err)}` });
449
+ }
450
+ if (verdict.decision === "allow") return decide("allow", verdict.input ? { updatedInput: verdict.input } : {});
451
+ if (verdict.decision === "deny") return decide("deny", { permissionDecisionReason: verdict.reason });
452
+ this.#asked.set(hookInput.tool_use_id, {
453
+ ...verdict.ruleId !== void 0 && { ruleId: verdict.ruleId },
454
+ ...verdict.reason !== void 0 && { reason: verdict.reason }
455
+ });
456
+ return decide("ask");
457
+ };
458
+ #canUseTool = (toolName, input, { signal, toolUseID }) => {
459
+ if (this.#closed) return Promise.resolve({
460
+ behavior: "deny",
461
+ message: "Session is closed"
462
+ });
463
+ let label = {};
464
+ if (this.#gate) {
465
+ const asked = this.#asked.get(toolUseID);
466
+ if (!asked) return Promise.resolve({
467
+ behavior: "deny",
468
+ message: `Tool "${toolName}" was not cleared by the tool gateway`
469
+ });
470
+ this.#asked.delete(toolUseID);
471
+ label = asked;
472
+ } else {
473
+ if (this.#sessionAllowed.has(toolName)) return Promise.resolve({ behavior: "allow" });
474
+ if (!this.#askTools.has(toolName)) return Promise.resolve({
475
+ behavior: "deny",
476
+ message: `Tool "${toolName}" is not permitted by this app`
477
+ });
478
+ }
479
+ const approvalId = toolUseID || randomUUID();
480
+ return new Promise((resolve) => {
481
+ this.#pendingApprovals.set(approvalId, {
482
+ tool: toolName,
483
+ resolve
484
+ });
485
+ signal.addEventListener("abort", () => {
486
+ this.#settleApproval(approvalId, { behavior: "deny" }, "session_closed");
487
+ });
488
+ this.#pushOutOfBand({
489
+ type: "approval_required",
490
+ approvalId,
491
+ tool: toolName,
492
+ input,
493
+ ...label
494
+ });
495
+ });
496
+ };
497
+ async respondToApproval(approvalId, decision) {
498
+ if (!this.#pendingApprovals.has(approvalId)) throw new AppSdkError(`Unknown or already resolved approval "${approvalId}"`);
499
+ const reason = decision.behavior === "deny" ? decision.reason ?? "user" : "user";
500
+ this.#settleApproval(approvalId, decision, reason);
501
+ }
502
+ #settleApproval(approvalId, decision, reason) {
503
+ const pending = this.#pendingApprovals.get(approvalId);
504
+ if (!pending) return;
505
+ this.#pendingApprovals.delete(approvalId);
506
+ if (decision.behavior === "allow") {
507
+ if (!this.#gate && (decision.scope === "session" || decision.scope === "always")) this.#sessionAllowed.add(pending.tool);
508
+ pending.resolve({ behavior: "allow" });
509
+ } else pending.resolve({
510
+ behavior: "deny",
511
+ message: decision.message ?? "Denied by user"
512
+ });
513
+ this.#pushOutOfBand({
514
+ type: "approval_resolved",
515
+ approvalId,
516
+ behavior: decision.behavior,
517
+ reason
518
+ });
519
+ }
520
+ #pushOutOfBand(event) {
521
+ this.#outOfBand.push(event);
522
+ this.#wakeOutOfBand?.();
523
+ }
524
+ #denyAllPending() {
525
+ for (const approvalId of [...this.#pendingApprovals.keys()]) this.#settleApproval(approvalId, { behavior: "deny" }, "session_closed");
526
+ }
401
527
  async *#run(input) {
402
528
  try {
403
529
  this.#queue.push({
@@ -409,7 +535,16 @@ var ClaudeSession = class {
409
535
  parent_tool_use_id: null
410
536
  });
411
537
  while (true) {
412
- const next = await this.#iterator.next();
538
+ yield* this.#outOfBand.splice(0);
539
+ this.#sdkNext ??= this.#iterator.next();
540
+ const wake = new Promise((resolve) => {
541
+ this.#wakeOutOfBand = resolve;
542
+ });
543
+ if (this.#outOfBand.length > 0) continue;
544
+ const winner = await Promise.race([this.#sdkNext.then((result) => ({ sdk: result })), wake.then(() => void 0)]);
545
+ if (winner === void 0) continue;
546
+ this.#sdkNext = void 0;
547
+ const next = winner.sdk;
413
548
  if (next.done) {
414
549
  yield {
415
550
  type: "result",
@@ -423,24 +558,32 @@ var ClaudeSession = class {
423
558
  if (event.type === "session") this.#id = event.sessionId;
424
559
  yield event;
425
560
  }
426
- if (message.type === "result") return;
561
+ if (message.type === "result") {
562
+ yield* this.#outOfBand.splice(0);
563
+ return;
564
+ }
427
565
  }
428
566
  } finally {
567
+ this.#wakeOutOfBand = void 0;
429
568
  this.#busy = false;
430
569
  }
431
570
  }
432
571
  async interrupt() {
572
+ this.#denyAllPending();
433
573
  await this.#query.interrupt();
434
574
  }
435
575
  async close() {
436
576
  if (this.#closed) return;
437
577
  this.#closed = true;
578
+ this.#denyAllPending();
438
579
  this.#queue.end();
439
580
  await this.#iterator.return?.(void 0)?.catch(() => void 0);
440
581
  }
441
582
  };
442
583
  var ClaudeCodeProvider = class {
443
584
  name = "claude-code";
585
+ supportsApprovals = true;
586
+ supportsToolGate = true;
444
587
  #defaults;
445
588
  #projectsDir;
446
589
  constructor(options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["#costUsd","#models","#done","#resolvers","#values","#query","#queue","#iterator","#id","#closed","#busy","#run","#tally","#defaults","#projectsDir"],"sources":["../src/event-mapping.ts","../src/history.ts","../src/message-queue.ts","../src/skills-plugin.ts","../src/transcripts.ts","../src/claude-provider.ts"],"sourcesContent":["import type { AgentEvent, ModelTokenUsage } from '@alashi/apps-sdk';\nimport type { ModelUsage, SDKMessage } from '@anthropic-ai/claude-agent-sdk';\n\n/**\n * Converts the Agent SDK's per-session usage totals into per-turn deltas.\n * `total_cost_usd` and `modelUsage` on result messages are cumulative across\n * the turns of a streaming-input session, so recording them as-is would\n * count early turns once per subsequent turn. Each session owns one tally;\n * a total that shrinks (mid-session /clear) is treated as a fresh start.\n */\nexport class UsageTally {\n #costUsd = 0;\n #models = new Map<string, ModelUsage>();\n\n costDelta(total: number | undefined): number | undefined {\n if (typeof total !== 'number' || !Number.isFinite(total)) return undefined;\n const delta = total >= this.#costUsd ? total - this.#costUsd : total;\n this.#costUsd = total;\n return delta;\n }\n\n modelUsageDelta(\n totals: Record<string, ModelUsage> | undefined,\n ): Record<string, ModelTokenUsage> | undefined {\n if (totals === undefined) return undefined;\n const deltas: Record<string, ModelTokenUsage> = {};\n for (const [model, cur] of Object.entries(totals)) {\n const prev = this.#models.get(model);\n this.#models.set(model, cur);\n // A shrinking token total means the session's counters restarted, so\n // the current totals themselves are this turn's usage.\n const reset = prev !== undefined && tokenSum(cur) < tokenSum(prev);\n const base = prev !== undefined && !reset ? prev : undefined;\n const delta: ModelTokenUsage = {\n inputTokens: diff(cur.inputTokens, base?.inputTokens),\n outputTokens: diff(cur.outputTokens, base?.outputTokens),\n cacheReadInputTokens: diff(cur.cacheReadInputTokens, base?.cacheReadInputTokens),\n cacheCreationInputTokens: diff(\n cur.cacheCreationInputTokens,\n base?.cacheCreationInputTokens,\n ),\n costUsd: diff(cur.costUSD, base?.costUSD),\n };\n if (\n delta.inputTokens > 0 ||\n delta.outputTokens > 0 ||\n (delta.cacheReadInputTokens ?? 0) > 0 ||\n (delta.cacheCreationInputTokens ?? 0) > 0 ||\n (delta.costUsd ?? 0) > 0\n ) {\n deltas[model] = delta;\n }\n }\n return Object.keys(deltas).length > 0 ? deltas : undefined;\n }\n}\n\nfunction tokenSum(usage: ModelUsage): number {\n return (\n usage.inputTokens +\n usage.outputTokens +\n usage.cacheReadInputTokens +\n usage.cacheCreationInputTokens\n );\n}\n\nfunction diff(cur: number | undefined, prev: number | undefined): number {\n if (typeof cur !== 'number' || !Number.isFinite(cur)) return 0;\n return Math.max(0, cur - (prev ?? 0));\n}\n\n/**\n * Maps a raw Agent SDK message to zero or more provider-neutral events.\n * With includePartialMessages on, text/thinking arrive as stream_event\n * deltas, so the whole-message assistant blocks are skipped to avoid\n * emitting the same text twice. When a `tally` is given, result events\n * report per-turn cost/token deltas instead of the SDK's session totals.\n */\nexport function mapMessage(message: SDKMessage, tally?: UsageTally): AgentEvent[] {\n switch (message.type) {\n case 'system': {\n if (message.subtype === 'init') {\n return [{ type: 'session', sessionId: message.session_id, model: message.model }];\n }\n return [];\n }\n case 'stream_event': {\n const event = message.event;\n if (event.type === 'content_block_delta') {\n if (event.delta.type === 'text_delta') {\n return [{ type: 'text', text: event.delta.text }];\n }\n if (event.delta.type === 'thinking_delta') {\n return [{ type: 'thinking', text: event.delta.thinking }];\n }\n }\n return [];\n }\n case 'assistant': {\n const events: AgentEvent[] = [];\n for (const block of message.message.content) {\n if (block.type === 'tool_use') {\n events.push({ type: 'tool_use', name: block.name, input: block.input });\n }\n }\n return events;\n }\n case 'user': {\n const content = message.message.content;\n if (!Array.isArray(content)) return [];\n const events: AgentEvent[] = [];\n for (const block of content) {\n if (typeof block === 'object' && block !== null && block.type === 'tool_result') {\n events.push({\n type: 'tool_result',\n output: block.content,\n isError: block.is_error ?? false,\n });\n }\n }\n return events;\n }\n case 'result': {\n const costUsd = tally ? tally.costDelta(message.total_cost_usd) : message.total_cost_usd;\n const modelUsage = tally?.modelUsageDelta(message.modelUsage);\n if (message.subtype === 'success') {\n return [\n {\n type: 'result',\n ok: !message.is_error,\n text: message.result,\n costUsd,\n turns: message.num_turns,\n ...(modelUsage !== undefined && { modelUsage }),\n },\n ];\n }\n return [\n {\n type: 'result',\n ok: false,\n error: message.subtype,\n costUsd,\n turns: message.num_turns,\n ...(modelUsage !== undefined && { modelUsage }),\n },\n ];\n }\n default:\n return [];\n }\n}\n","import { readdir, readFile, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ChatMessage } from '@alashi/apps-sdk';\n\n// Also a traversal guard: sessionId is used as a filename.\nexport const SESSION_ID_PATTERN: RegExp =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\ninterface TranscriptLine {\n type?: string;\n isMeta?: boolean;\n timestamp?: string;\n message?: {\n role?: string;\n content?: unknown;\n };\n}\n\nexport function defaultProjectsDir(): string {\n return join(homedir(), '.claude', 'projects');\n}\n\n/**\n * Session transcripts live under a project dir named after the session's cwd\n * (see `projectDirName` in transcripts.ts), but a session may have been started\n * with a different cwd than the one we are asked about, so scan every project\n * dir rather than computing a single candidate path.\n */\nexport async function findSessionFile(\n projectsDir: string,\n sessionId: string,\n): Promise<string | undefined> {\n let dirs: string[];\n try {\n dirs = await readdir(projectsDir);\n } catch {\n return undefined;\n }\n for (const dir of dirs) {\n const candidate = join(projectsDir, dir, `${sessionId}.jsonl`);\n try {\n await stat(candidate);\n return candidate;\n } catch {\n // Not in this project dir; keep scanning.\n }\n }\n return undefined;\n}\n\nfunction textFromContent(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .filter(\n (block): block is { type: 'text'; text: string } =>\n typeof block === 'object' &&\n block !== null &&\n (block as { type?: string }).type === 'text' &&\n typeof (block as { text?: unknown }).text === 'string',\n )\n .map((block) => block.text)\n .join('');\n}\n\nfunction toChatMessage(line: TranscriptLine): ChatMessage | undefined {\n if (line.isMeta) return undefined;\n if (line.type !== 'user' && line.type !== 'assistant') return undefined;\n const role = line.type;\n if (line.message?.role !== role) return undefined;\n const text = textFromContent(line.message.content);\n // Tool-result user lines and tool-use-only assistant lines have no text.\n if (text === '') return undefined;\n return { role, text, ...(line.timestamp && { timestamp: line.timestamp }) };\n}\n\n/**\n * Reads a session's past messages from Claude Code's own transcript\n * (~/.claude/projects/<project>/<sessionId>.jsonl). The JSONL layout is a\n * Claude Code internal, so parsing is deliberately forgiving: unknown or\n * malformed lines are skipped rather than failing the whole read.\n */\nexport async function readSessionHistory(\n sessionId: string,\n projectsDir: string = defaultProjectsDir(),\n): Promise<ChatMessage[]> {\n if (!SESSION_ID_PATTERN.test(sessionId)) return [];\n const file = await findSessionFile(projectsDir, sessionId);\n if (!file) return [];\n\n const messages: ChatMessage[] = [];\n for (const line of (await readFile(file, 'utf8')).split('\\n')) {\n if (line.trim() === '') continue;\n let parsed: TranscriptLine;\n try {\n parsed = JSON.parse(line) as TranscriptLine;\n } catch {\n continue;\n }\n const message = toChatMessage(parsed);\n if (message) messages.push(message);\n }\n return messages;\n}\n","/** Push-based async iterable used as the streaming-input prompt for query(). */\nexport class AsyncMessageQueue<T> implements AsyncIterable<T> {\n #values: T[] = [];\n #resolvers: Array<(result: IteratorResult<T>) => void> = [];\n #done = false;\n\n push(value: T): void {\n if (this.#done) throw new Error('Queue is closed');\n const resolver = this.#resolvers.shift();\n if (resolver) resolver({ value, done: false });\n else this.#values.push(value);\n }\n\n end(): void {\n if (this.#done) return;\n this.#done = true;\n for (const resolver of this.#resolvers.splice(0)) {\n resolver({ value: undefined, done: true });\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n const value = this.#values.shift();\n if (value !== undefined) return Promise.resolve({ value, done: false });\n if (this.#done) return Promise.resolve({ value: undefined, done: true });\n return new Promise((resolve) => {\n this.#resolvers.push(resolve);\n });\n },\n };\n }\n}\n","import { createHash } from 'node:crypto';\nimport { mkdir, readdir, rm, stat, symlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, join } from 'node:path';\n\n/**\n * With settingSources [] the engine discovers skills only through plugins,\n * so app-declared skill dirs are wrapped in a synthesized local plugin whose\n * skills/ dir symlinks them. Each declared dir may be a single skill\n * (contains SKILL.md) or a parent holding several skill folders.\n */\n\nconst cache = new Map<string, Promise<string>>();\n\nasync function isSkill(dir: string): Promise<boolean> {\n return stat(join(dir, 'SKILL.md')).then(\n (s) => s.isFile(),\n () => false,\n );\n}\n\nasync function build(pluginDir: string, skillDirs: string[]): Promise<string> {\n // Rebuild from scratch: symlinks are cheap and stale links are worse.\n await rm(pluginDir, { recursive: true, force: true });\n const skillsDir = join(pluginDir, 'skills');\n await mkdir(join(pluginDir, '.claude-plugin'), { recursive: true });\n await mkdir(skillsDir, { recursive: true });\n await writeFile(\n join(pluginDir, '.claude-plugin', 'plugin.json'),\n JSON.stringify({\n name: 'alashi-app-skills',\n description: 'Skills declared by the alashi app manifest',\n }),\n 'utf8',\n );\n for (const dir of skillDirs) {\n if (await isSkill(dir)) {\n await symlink(dir, join(skillsDir, basename(dir)), 'dir');\n continue;\n }\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const child = join(dir, entry.name);\n if (await isSkill(child)) await symlink(child, join(skillsDir, entry.name), 'dir');\n }\n }\n return pluginDir;\n}\n\n/** Plugin dir for a set of skill dirs; built once per process per set. */\nexport function ensureSkillsPlugin(skillDirs: string[]): Promise<string> {\n const key = createHash('sha256')\n .update(JSON.stringify([...skillDirs].sort()))\n .digest('hex')\n .slice(0, 12);\n let plugin = cache.get(key);\n if (!plugin) {\n plugin = build(join(tmpdir(), `alashi-skills-${key}`), skillDirs);\n plugin.catch(() => cache.delete(key));\n cache.set(key, plugin);\n }\n return plugin;\n}\n","import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { SessionTranscript } from '@alashi/apps-sdk';\nimport { findSessionFile, SESSION_ID_PATTERN } from './history.js';\n\n/**\n * Claude Code stores one JSONL transcript per session at\n * `~/.claude/projects/<project>/<sessionId>.jsonl`, where `<project>` is the\n * session's cwd with every non-alphanumeric character replaced by `-`:\n * `/Users/x/.alashi/apps/sre-app/data` -> `-Users-x--alashi-apps-sre-app-data`\n * (note the doubled `-` where `/.` appears). The munging is a Claude Code\n * internal, established empirically.\n */\nexport function projectDirName(cwd: string): string {\n return cwd.replace(/[^a-zA-Z0-9]/g, '-');\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Raw transcripts for the given sessions, in input order. Ids with no\n * transcript on disk are omitted (the contract treats unknown ids as absent,\n * not as an error), as are ids that fail the session-id guard.\n */\nexport async function exportSessionTranscripts(\n sessionIds: readonly string[],\n projectsDir: string,\n): Promise<SessionTranscript[]> {\n const transcripts: SessionTranscript[] = [];\n for (const sessionId of sessionIds) {\n if (!SESSION_ID_PATTERN.test(sessionId)) continue;\n const file = await findSessionFile(projectsDir, sessionId);\n if (!file) continue;\n try {\n transcripts.push({ sessionId, content: await readFile(file, 'utf8') });\n } catch {\n // Vanished or unreadable between the scan and the read: treat as absent.\n }\n }\n return transcripts;\n}\n\n/**\n * Restores transcripts into the project dir for `cwd` on *this* machine.\n *\n * Never overwrites: the engine's own state is authoritative, so an import must\n * not clobber a live session's transcript. Ids that fail the session-id guard\n * are skipped rather than throwing, so one bad entry cannot abort a restore.\n * The `cwd` values recorded *inside* the JSONL lines are left alone — they are\n * a Claude Code internal and the history reader does not consult them.\n */\nexport async function importSessionTranscripts(\n transcripts: readonly SessionTranscript[],\n options: { cwd: string; projectsDir: string },\n): Promise<void> {\n const writable = transcripts.filter((t) => SESSION_ID_PATTERN.test(t.sessionId));\n if (writable.length === 0) return;\n\n const projectDir = join(options.projectsDir, projectDirName(options.cwd));\n await mkdir(projectDir, { recursive: true });\n for (const { sessionId, content } of writable) {\n const dest = join(projectDir, `${sessionId}.jsonl`);\n if (await exists(dest)) continue;\n // Temp file + rename, so a reader never sees a half-written transcript\n // (same pattern as the host's store writeJson).\n const tmp = `${dest}.tmp`;\n await writeFile(tmp, content, 'utf8');\n await rename(tmp, dest);\n }\n}\n","import {\n type AgentEvent,\n type AIProvider,\n type AISession,\n AppSdkError,\n type ChatMessage,\n type SessionOptions,\n type SessionTranscript,\n} from '@alashi/apps-sdk';\nimport {\n type Options,\n type Query,\n query,\n type SDKMessage,\n type SDKUserMessage,\n} from '@anthropic-ai/claude-agent-sdk';\nimport { mapMessage, UsageTally } from './event-mapping.js';\nimport { defaultProjectsDir, readSessionHistory } from './history.js';\nimport { AsyncMessageQueue } from './message-queue.js';\nimport { ensureSkillsPlugin } from './skills-plugin.js';\nimport { exportSessionTranscripts, importSessionTranscripts } from './transcripts.js';\n\nexport interface ClaudeCodeProviderOptions {\n /** Defaults applied to every session; per-session options win. */\n sessionDefaults?: Partial<SessionOptions>;\n /** Where Claude Code stores session transcripts (for history reads). */\n claudeProjectsDir?: string;\n}\n\nfunction toQueryOptions(options: SessionOptions, skillsPluginDir?: string): Options {\n const out: Options = {\n // Non-interactive: manifest-declared allowedTools are approved, everything else denied.\n permissionMode: 'dontAsk',\n // Apps run isolated from the user's personal/project Claude Code settings.\n settingSources: [],\n // Text arrives as stream_event deltas (see event-mapping.ts).\n includePartialMessages: true,\n };\n if (options.systemPrompt !== undefined) out.systemPrompt = options.systemPrompt;\n if (options.model !== undefined) out.model = options.model;\n if (options.mcpServers !== undefined) out.mcpServers = options.mcpServers;\n if (options.allowedTools !== undefined) out.allowedTools = options.allowedTools;\n if (options.maxTurns !== undefined) out.maxTurns = options.maxTurns;\n if (options.cwd !== undefined) out.cwd = options.cwd;\n if (options.resume !== undefined) out.resume = options.resume;\n if (options.agents !== undefined && Object.keys(options.agents).length > 0) {\n out.agents = options.agents;\n // Subagents are invoked through the Agent tool; approve it alongside them.\n out.allowedTools = [...new Set([...(out.allowedTools ?? []), 'Agent'])];\n }\n if (skillsPluginDir !== undefined) {\n out.plugins = [{ type: 'local', path: skillsPluginDir }];\n out.skills = 'all';\n }\n return out;\n}\n\n/**\n * One long-lived Claude Code session per chat (streaming input mode), so\n * follow-up turns reuse the process and its prompt cache instead of paying\n * session startup per message. Turns are serialized: one send() at a time.\n */\nclass ClaudeSession implements AISession {\n #queue: AsyncMessageQueue<SDKUserMessage> = new AsyncMessageQueue();\n #query: Query;\n #iterator: AsyncIterator<SDKMessage>;\n #id = '';\n #busy = false;\n #closed = false;\n // The SDK reports cost/tokens as session-cumulative totals; the tally\n // turns them into the per-turn deltas the AgentEvent contract promises.\n #tally = new UsageTally();\n\n constructor(options: SessionOptions, skillsPluginDir?: string) {\n this.#query = query({ prompt: this.#queue, options: toQueryOptions(options, skillsPluginDir) });\n this.#iterator = this.#query[Symbol.asyncIterator]();\n }\n\n get id(): string {\n return this.#id;\n }\n\n send(input: string): AsyncIterable<AgentEvent> {\n if (this.#closed) throw new AppSdkError('Session is closed');\n if (this.#busy) throw new AppSdkError('Session is busy with another turn');\n this.#busy = true;\n return this.#run(input);\n }\n\n async *#run(input: string): AsyncGenerator<AgentEvent> {\n try {\n this.#queue.push({\n type: 'user',\n message: { role: 'user', content: input },\n parent_tool_use_id: null,\n });\n while (true) {\n const next = await this.#iterator.next();\n if (next.done) {\n yield { type: 'result', ok: false, error: 'Session ended unexpectedly' };\n return;\n }\n const message = next.value;\n for (const event of mapMessage(message, this.#tally)) {\n if (event.type === 'session') this.#id = event.sessionId;\n yield event;\n }\n if (message.type === 'result') return;\n }\n } finally {\n this.#busy = false;\n }\n }\n\n async interrupt(): Promise<void> {\n await this.#query.interrupt();\n }\n\n async close(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n this.#queue.end();\n await this.#iterator.return?.(undefined)?.catch(() => undefined);\n }\n}\n\nexport class ClaudeCodeProvider implements AIProvider {\n readonly name: string = 'claude-code';\n readonly #defaults: Partial<SessionOptions>;\n readonly #projectsDir: string | undefined;\n\n constructor(options: ClaudeCodeProviderOptions = {}) {\n this.#defaults = options.sessionDefaults ?? {};\n this.#projectsDir = options.claudeProjectsDir;\n }\n\n async createSession(options: SessionOptions = {}): Promise<AISession> {\n const merged = { ...this.#defaults, ...options };\n const skillsPluginDir = merged.skillDirs?.length\n ? await ensureSkillsPlugin(merged.skillDirs)\n : undefined;\n return new ClaudeSession(merged, skillsPluginDir);\n }\n\n async history(sessionId: string): Promise<ChatMessage[]> {\n return readSessionHistory(sessionId, this.#projectsDir ?? defaultProjectsDir());\n }\n\n /** Snapshots Claude Code's own JSONL transcripts; unknown ids are omitted. */\n async exportTranscripts(sessionIds: string[]): Promise<SessionTranscript[]> {\n return exportSessionTranscripts(sessionIds, this.#projectsDir ?? defaultProjectsDir());\n }\n\n /**\n * Restores transcripts into the project dir Claude Code derives from `cwd`\n * (the app's data dir on this machine). Existing transcripts are left alone.\n */\n async importTranscripts(\n transcripts: SessionTranscript[],\n options: { cwd: string },\n ): Promise<void> {\n return importSessionTranscripts(transcripts, {\n cwd: options.cwd,\n projectsDir: this.#projectsDir ?? defaultProjectsDir(),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["#costUsd","#models","#done","#resolvers","#values","#askTools","#gate","#canUseTool","#query","#queue","#gateHook","#iterator","#id","#closed","#busy","#run","#asked","#sessionAllowed","#pendingApprovals","#settleApproval","#pushOutOfBand","#outOfBand","#wakeOutOfBand","#sdkNext","#tally","#denyAllPending","#defaults","#projectsDir"],"sources":["../src/event-mapping.ts","../src/history.ts","../src/message-queue.ts","../src/skills-plugin.ts","../src/transcripts.ts","../src/claude-provider.ts"],"sourcesContent":["import type { AgentEvent, ModelTokenUsage } from '@alashi/apps-sdk';\nimport type { ModelUsage, SDKMessage } from '@anthropic-ai/claude-agent-sdk';\n\n/**\n * Converts the Agent SDK's per-session usage totals into per-turn deltas.\n * `total_cost_usd` and `modelUsage` on result messages are cumulative across\n * the turns of a streaming-input session, so recording them as-is would\n * count early turns once per subsequent turn. Each session owns one tally;\n * a total that shrinks (mid-session /clear) is treated as a fresh start.\n */\nexport class UsageTally {\n #costUsd = 0;\n #models = new Map<string, ModelUsage>();\n\n costDelta(total: number | undefined): number | undefined {\n if (typeof total !== 'number' || !Number.isFinite(total)) return undefined;\n const delta = total >= this.#costUsd ? total - this.#costUsd : total;\n this.#costUsd = total;\n return delta;\n }\n\n modelUsageDelta(\n totals: Record<string, ModelUsage> | undefined,\n ): Record<string, ModelTokenUsage> | undefined {\n if (totals === undefined) return undefined;\n const deltas: Record<string, ModelTokenUsage> = {};\n for (const [model, cur] of Object.entries(totals)) {\n const prev = this.#models.get(model);\n this.#models.set(model, cur);\n // A shrinking token total means the session's counters restarted, so\n // the current totals themselves are this turn's usage.\n const reset = prev !== undefined && tokenSum(cur) < tokenSum(prev);\n const base = prev !== undefined && !reset ? prev : undefined;\n const delta: ModelTokenUsage = {\n inputTokens: diff(cur.inputTokens, base?.inputTokens),\n outputTokens: diff(cur.outputTokens, base?.outputTokens),\n cacheReadInputTokens: diff(cur.cacheReadInputTokens, base?.cacheReadInputTokens),\n cacheCreationInputTokens: diff(\n cur.cacheCreationInputTokens,\n base?.cacheCreationInputTokens,\n ),\n costUsd: diff(cur.costUSD, base?.costUSD),\n };\n if (\n delta.inputTokens > 0 ||\n delta.outputTokens > 0 ||\n (delta.cacheReadInputTokens ?? 0) > 0 ||\n (delta.cacheCreationInputTokens ?? 0) > 0 ||\n (delta.costUsd ?? 0) > 0\n ) {\n deltas[model] = delta;\n }\n }\n return Object.keys(deltas).length > 0 ? deltas : undefined;\n }\n}\n\nfunction tokenSum(usage: ModelUsage): number {\n return (\n usage.inputTokens +\n usage.outputTokens +\n usage.cacheReadInputTokens +\n usage.cacheCreationInputTokens\n );\n}\n\nfunction diff(cur: number | undefined, prev: number | undefined): number {\n if (typeof cur !== 'number' || !Number.isFinite(cur)) return 0;\n return Math.max(0, cur - (prev ?? 0));\n}\n\n/**\n * Maps a raw Agent SDK message to zero or more provider-neutral events.\n * With includePartialMessages on, text/thinking arrive as stream_event\n * deltas, so the whole-message assistant blocks are skipped to avoid\n * emitting the same text twice. When a `tally` is given, result events\n * report per-turn cost/token deltas instead of the SDK's session totals.\n */\nexport function mapMessage(message: SDKMessage, tally?: UsageTally): AgentEvent[] {\n switch (message.type) {\n case 'system': {\n if (message.subtype === 'init') {\n return [{ type: 'session', sessionId: message.session_id, model: message.model }];\n }\n if (message.subtype === 'compact_boundary') {\n const meta = message.compact_metadata;\n return [\n {\n type: 'compaction',\n trigger: meta.trigger,\n preTokens: meta.pre_tokens,\n ...(meta.post_tokens !== undefined && { postTokens: meta.post_tokens }),\n ...(meta.duration_ms !== undefined && { durationMs: meta.duration_ms }),\n },\n ];\n }\n return [];\n }\n case 'stream_event': {\n const event = message.event;\n if (event.type === 'content_block_delta') {\n if (event.delta.type === 'text_delta') {\n return [{ type: 'text', text: event.delta.text }];\n }\n if (event.delta.type === 'thinking_delta') {\n return [{ type: 'thinking', text: event.delta.thinking }];\n }\n }\n return [];\n }\n case 'assistant': {\n const events: AgentEvent[] = [];\n for (const block of message.message.content) {\n if (block.type === 'tool_use') {\n events.push({ type: 'tool_use', name: block.name, input: block.input });\n }\n }\n return events;\n }\n case 'user': {\n const content = message.message.content;\n if (!Array.isArray(content)) return [];\n const events: AgentEvent[] = [];\n for (const block of content) {\n if (typeof block === 'object' && block !== null && block.type === 'tool_result') {\n events.push({\n type: 'tool_result',\n output: block.content,\n isError: block.is_error ?? false,\n });\n }\n }\n return events;\n }\n case 'result': {\n const costUsd = tally ? tally.costDelta(message.total_cost_usd) : message.total_cost_usd;\n const modelUsage = tally?.modelUsageDelta(message.modelUsage);\n if (message.subtype === 'success') {\n return [\n {\n type: 'result',\n ok: !message.is_error,\n text: message.result,\n costUsd,\n turns: message.num_turns,\n ...(modelUsage !== undefined && { modelUsage }),\n },\n ];\n }\n return [\n {\n type: 'result',\n ok: false,\n error: message.subtype,\n costUsd,\n turns: message.num_turns,\n ...(modelUsage !== undefined && { modelUsage }),\n },\n ];\n }\n default:\n return [];\n }\n}\n","import { readdir, readFile, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { ChatMessage } from '@alashi/apps-sdk';\n\n// Also a traversal guard: sessionId is used as a filename.\nexport const SESSION_ID_PATTERN: RegExp =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;\n\ninterface TranscriptLine {\n type?: string;\n isMeta?: boolean;\n timestamp?: string;\n message?: {\n role?: string;\n content?: unknown;\n };\n}\n\nexport function defaultProjectsDir(): string {\n return join(homedir(), '.claude', 'projects');\n}\n\n/**\n * Session transcripts live under a project dir named after the session's cwd\n * (see `projectDirName` in transcripts.ts), but a session may have been started\n * with a different cwd than the one we are asked about, so scan every project\n * dir rather than computing a single candidate path.\n */\nexport async function findSessionFile(\n projectsDir: string,\n sessionId: string,\n): Promise<string | undefined> {\n let dirs: string[];\n try {\n dirs = await readdir(projectsDir);\n } catch {\n return undefined;\n }\n for (const dir of dirs) {\n const candidate = join(projectsDir, dir, `${sessionId}.jsonl`);\n try {\n await stat(candidate);\n return candidate;\n } catch {\n // Not in this project dir; keep scanning.\n }\n }\n return undefined;\n}\n\nfunction textFromContent(content: unknown): string {\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return '';\n return content\n .filter(\n (block): block is { type: 'text'; text: string } =>\n typeof block === 'object' &&\n block !== null &&\n (block as { type?: string }).type === 'text' &&\n typeof (block as { text?: unknown }).text === 'string',\n )\n .map((block) => block.text)\n .join('');\n}\n\nfunction toChatMessage(line: TranscriptLine): ChatMessage | undefined {\n if (line.isMeta) return undefined;\n if (line.type !== 'user' && line.type !== 'assistant') return undefined;\n const role = line.type;\n if (line.message?.role !== role) return undefined;\n const text = textFromContent(line.message.content);\n // Tool-result user lines and tool-use-only assistant lines have no text.\n if (text === '') return undefined;\n return { role, text, ...(line.timestamp && { timestamp: line.timestamp }) };\n}\n\n/**\n * Reads a session's past messages from Claude Code's own transcript\n * (~/.claude/projects/<project>/<sessionId>.jsonl). The JSONL layout is a\n * Claude Code internal, so parsing is deliberately forgiving: unknown or\n * malformed lines are skipped rather than failing the whole read.\n */\nexport async function readSessionHistory(\n sessionId: string,\n projectsDir: string = defaultProjectsDir(),\n): Promise<ChatMessage[]> {\n if (!SESSION_ID_PATTERN.test(sessionId)) return [];\n const file = await findSessionFile(projectsDir, sessionId);\n if (!file) return [];\n\n const messages: ChatMessage[] = [];\n for (const line of (await readFile(file, 'utf8')).split('\\n')) {\n if (line.trim() === '') continue;\n let parsed: TranscriptLine;\n try {\n parsed = JSON.parse(line) as TranscriptLine;\n } catch {\n continue;\n }\n const message = toChatMessage(parsed);\n if (message) messages.push(message);\n }\n return messages;\n}\n","/** Push-based async iterable used as the streaming-input prompt for query(). */\nexport class AsyncMessageQueue<T> implements AsyncIterable<T> {\n #values: T[] = [];\n #resolvers: Array<(result: IteratorResult<T>) => void> = [];\n #done = false;\n\n push(value: T): void {\n if (this.#done) throw new Error('Queue is closed');\n const resolver = this.#resolvers.shift();\n if (resolver) resolver({ value, done: false });\n else this.#values.push(value);\n }\n\n end(): void {\n if (this.#done) return;\n this.#done = true;\n for (const resolver of this.#resolvers.splice(0)) {\n resolver({ value: undefined, done: true });\n }\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n const value = this.#values.shift();\n if (value !== undefined) return Promise.resolve({ value, done: false });\n if (this.#done) return Promise.resolve({ value: undefined, done: true });\n return new Promise((resolve) => {\n this.#resolvers.push(resolve);\n });\n },\n };\n }\n}\n","import { createHash } from 'node:crypto';\nimport { mkdir, readdir, rm, stat, symlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { basename, join } from 'node:path';\n\n/**\n * With settingSources [] the engine discovers skills only through plugins,\n * so app-declared skill dirs are wrapped in a synthesized local plugin whose\n * skills/ dir symlinks them. Each declared dir may be a single skill\n * (contains SKILL.md) or a parent holding several skill folders.\n */\n\nconst cache = new Map<string, Promise<string>>();\n\nasync function isSkill(dir: string): Promise<boolean> {\n return stat(join(dir, 'SKILL.md')).then(\n (s) => s.isFile(),\n () => false,\n );\n}\n\nasync function build(pluginDir: string, skillDirs: string[]): Promise<string> {\n // Rebuild from scratch: symlinks are cheap and stale links are worse.\n await rm(pluginDir, { recursive: true, force: true });\n const skillsDir = join(pluginDir, 'skills');\n await mkdir(join(pluginDir, '.claude-plugin'), { recursive: true });\n await mkdir(skillsDir, { recursive: true });\n await writeFile(\n join(pluginDir, '.claude-plugin', 'plugin.json'),\n JSON.stringify({\n name: 'alashi-app-skills',\n description: 'Skills declared by the alashi app manifest',\n }),\n 'utf8',\n );\n for (const dir of skillDirs) {\n if (await isSkill(dir)) {\n await symlink(dir, join(skillsDir, basename(dir)), 'dir');\n continue;\n }\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n const child = join(dir, entry.name);\n if (await isSkill(child)) await symlink(child, join(skillsDir, entry.name), 'dir');\n }\n }\n return pluginDir;\n}\n\n/** Plugin dir for a set of skill dirs; built once per process per set. */\nexport function ensureSkillsPlugin(skillDirs: string[]): Promise<string> {\n const key = createHash('sha256')\n .update(JSON.stringify([...skillDirs].sort()))\n .digest('hex')\n .slice(0, 12);\n let plugin = cache.get(key);\n if (!plugin) {\n plugin = build(join(tmpdir(), `alashi-skills-${key}`), skillDirs);\n plugin.catch(() => cache.delete(key));\n cache.set(key, plugin);\n }\n return plugin;\n}\n","import { mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { SessionTranscript } from '@alashi/apps-sdk';\nimport { findSessionFile, SESSION_ID_PATTERN } from './history.js';\n\n/**\n * Claude Code stores one JSONL transcript per session at\n * `~/.claude/projects/<project>/<sessionId>.jsonl`, where `<project>` is the\n * session's cwd with every non-alphanumeric character replaced by `-`:\n * `/Users/x/.alashi/apps/sre-app/data` -> `-Users-x--alashi-apps-sre-app-data`\n * (note the doubled `-` where `/.` appears). The munging is a Claude Code\n * internal, established empirically.\n */\nexport function projectDirName(cwd: string): string {\n return cwd.replace(/[^a-zA-Z0-9]/g, '-');\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await stat(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Raw transcripts for the given sessions, in input order. Ids with no\n * transcript on disk are omitted (the contract treats unknown ids as absent,\n * not as an error), as are ids that fail the session-id guard.\n */\nexport async function exportSessionTranscripts(\n sessionIds: readonly string[],\n projectsDir: string,\n): Promise<SessionTranscript[]> {\n const transcripts: SessionTranscript[] = [];\n for (const sessionId of sessionIds) {\n if (!SESSION_ID_PATTERN.test(sessionId)) continue;\n const file = await findSessionFile(projectsDir, sessionId);\n if (!file) continue;\n try {\n transcripts.push({ sessionId, content: await readFile(file, 'utf8') });\n } catch {\n // Vanished or unreadable between the scan and the read: treat as absent.\n }\n }\n return transcripts;\n}\n\n/**\n * Restores transcripts into the project dir for `cwd` on *this* machine.\n *\n * Never overwrites: the engine's own state is authoritative, so an import must\n * not clobber a live session's transcript. Ids that fail the session-id guard\n * are skipped rather than throwing, so one bad entry cannot abort a restore.\n * The `cwd` values recorded *inside* the JSONL lines are left alone — they are\n * a Claude Code internal and the history reader does not consult them.\n */\nexport async function importSessionTranscripts(\n transcripts: readonly SessionTranscript[],\n options: { cwd: string; projectsDir: string },\n): Promise<void> {\n const writable = transcripts.filter((t) => SESSION_ID_PATTERN.test(t.sessionId));\n if (writable.length === 0) return;\n\n const projectDir = join(options.projectsDir, projectDirName(options.cwd));\n await mkdir(projectDir, { recursive: true });\n for (const { sessionId, content } of writable) {\n const dest = join(projectDir, `${sessionId}.jsonl`);\n if (await exists(dest)) continue;\n // Temp file + rename, so a reader never sees a half-written transcript\n // (same pattern as the host's store writeJson).\n const tmp = `${dest}.tmp`;\n await writeFile(tmp, content, 'utf8');\n await rename(tmp, dest);\n }\n}\n","import { randomUUID } from 'node:crypto';\nimport {\n type AgentEvent,\n type AIProvider,\n type AISession,\n type ApprovalDecision,\n AppSdkError,\n type ChatMessage,\n type SessionOptions,\n type SessionTranscript,\n type ToolGate,\n} from '@alashi/apps-sdk';\nimport {\n type CanUseTool,\n type HookCallback,\n type Options,\n type PermissionResult,\n type Query,\n query,\n type SDKMessage,\n type SDKUserMessage,\n} from '@anthropic-ai/claude-agent-sdk';\nimport { mapMessage, UsageTally } from './event-mapping.js';\nimport { defaultProjectsDir, readSessionHistory } from './history.js';\nimport { AsyncMessageQueue } from './message-queue.js';\nimport { ensureSkillsPlugin } from './skills-plugin.js';\nimport { exportSessionTranscripts, importSessionTranscripts } from './transcripts.js';\n\nexport interface ClaudeCodeProviderOptions {\n /** Defaults applied to every session; per-session options win. */\n sessionDefaults?: Partial<SessionOptions>;\n /** Where Claude Code stores session transcripts (for history reads). */\n claudeProjectsDir?: string;\n}\n\n/** Engine-internal tools the gateway hook allows without consulting the host. */\nconst ENGINE_INTERNAL_TOOLS: ReadonlySet<string> = new Set(['ToolSearch']);\n\nfunction toQueryOptions(\n options: SessionOptions,\n skillsPluginDir?: string,\n canUseTool?: CanUseTool,\n gateHook?: HookCallback,\n): Options {\n const out: Options = canUseTool\n ? {\n // 'default' routes any tool outside allowedTools through canUseTool,\n // where ask-tier tools suspend for interactive approval.\n permissionMode: 'default',\n canUseTool,\n settingSources: [],\n includePartialMessages: true,\n }\n : {\n // Non-interactive: manifest-declared allowedTools are approved, everything else denied.\n permissionMode: 'dontAsk',\n // Apps run isolated from the user's personal/project Claude Code settings.\n settingSources: [],\n // Text arrives as stream_event deltas (see event-mapping.ts).\n includePartialMessages: true,\n };\n if (options.systemPrompt !== undefined) out.systemPrompt = options.systemPrompt;\n if (options.model !== undefined) out.model = options.model;\n if (options.mcpServers !== undefined) out.mcpServers = options.mcpServers;\n if (gateHook) {\n // Gateway mode: every call — built-in, MCP, subagent, even the engine's\n // \"safe\" read-only commands — passes the PreToolUse hook, and no engine\n // allowlist exists to skip it. The hook's `ask` falls through to\n // canUseTool for the interactive leg.\n out.hooks = { PreToolUse: [{ hooks: [gateHook] }] };\n } else if (options.allowedTools !== undefined) {\n out.allowedTools = options.allowedTools;\n }\n if (options.maxTurns !== undefined) out.maxTurns = options.maxTurns;\n if (options.cwd !== undefined) out.cwd = options.cwd;\n if (options.resume !== undefined) out.resume = options.resume;\n if (options.agents !== undefined && Object.keys(options.agents).length > 0) {\n out.agents = options.agents;\n // Subagents are invoked through the Agent tool; approve it alongside them\n // (gateway mode: the host's gate decides, like any other tool).\n if (!gateHook) out.allowedTools = [...new Set([...(out.allowedTools ?? []), 'Agent'])];\n }\n if (skillsPluginDir !== undefined) {\n out.plugins = [{ type: 'local', path: skillsPluginDir }];\n out.skills = 'all';\n }\n return out;\n}\n\n/**\n * One long-lived Claude Code session per chat (streaming input mode), so\n * follow-up turns reuse the process and its prompt cache instead of paying\n * session startup per message. Turns are serialized: one send() at a time.\n */\nclass ClaudeSession implements AISession {\n #queue: AsyncMessageQueue<SDKUserMessage> = new AsyncMessageQueue();\n #query: Query;\n #iterator: AsyncIterator<SDKMessage>;\n #id = '';\n #busy = false;\n #closed = false;\n // The SDK reports cost/tokens as session-cumulative totals; the tally\n // turns them into the per-turn deltas the AgentEvent contract promises.\n #tally = new UsageTally();\n #askTools: Set<string>;\n #gate: ToolGate | undefined;\n // Calls the gate answered `ask`, keyed by tool use id, awaiting canUseTool.\n #asked = new Map<string, { ruleId?: string; reason?: string }>();\n #sessionAllowed = new Set<string>();\n #pendingApprovals = new Map<\n string,\n { tool: string; resolve: (result: PermissionResult) => void }\n >();\n // Approval events are born outside the SDK message stream (the stream is\n // blocked inside canUseTool while one is pending), so they travel through\n // this side buffer and get merged into the turn's event stream by #run.\n #outOfBand: AgentEvent[] = [];\n #wakeOutOfBand: (() => void) | undefined;\n // Held across race iterations so the SDK iterator is never pulled twice.\n #sdkNext: Promise<IteratorResult<SDKMessage>> | undefined;\n\n constructor(options: SessionOptions, skillsPluginDir?: string) {\n this.#askTools = new Set(options.askTools ?? []);\n this.#gate = options.toolGate;\n const canUseTool = this.#gate || this.#askTools.size > 0 ? this.#canUseTool : undefined;\n this.#query = query({\n prompt: this.#queue,\n options: toQueryOptions(\n options,\n skillsPluginDir,\n canUseTool,\n this.#gate ? this.#gateHook : undefined,\n ),\n });\n this.#iterator = this.#query[Symbol.asyncIterator]();\n }\n\n get id(): string {\n return this.#id;\n }\n\n send(input: string): AsyncIterable<AgentEvent> {\n if (this.#closed) throw new AppSdkError('Session is closed');\n if (this.#busy) throw new AppSdkError('Session is busy with another turn');\n this.#busy = true;\n return this.#run(input);\n }\n\n /** PreToolUse hook: asks the host's gate about every tool call. */\n #gateHook: HookCallback = async (hookInput) => {\n if (hookInput.hook_event_name !== 'PreToolUse' || !this.#gate) return {};\n const decide = (\n permissionDecision: 'allow' | 'deny' | 'ask',\n extra: { permissionDecisionReason?: string; updatedInput?: Record<string, unknown> } = {},\n ) => ({\n hookSpecificOutput: { hookEventName: 'PreToolUse' as const, permissionDecision, ...extra },\n });\n if (this.#closed) return decide('deny', { permissionDecisionReason: 'Session is closed' });\n // Engine plumbing, not a capability: ToolSearch only loads deferred tool\n // schemas (every MCP tool needs it first), and the loaded tool's own call\n // still passes the gate. Gating it would make default-deny apps unable to\n // reach any MCP tool.\n if (ENGINE_INTERNAL_TOOLS.has(hookInput.tool_name)) return decide('allow');\n let verdict: Awaited<ReturnType<ToolGate>>;\n try {\n verdict = await this.#gate({\n tool: hookInput.tool_name,\n input: hookInput.tool_input,\n toolUseId: hookInput.tool_use_id,\n });\n } catch (err) {\n // Fail closed: a broken gateway must never let a call through.\n return decide('deny', { permissionDecisionReason: `Tool gateway error: ${String(err)}` });\n }\n if (verdict.decision === 'allow') {\n return decide('allow', verdict.input ? { updatedInput: verdict.input } : {});\n }\n if (verdict.decision === 'deny') {\n return decide('deny', { permissionDecisionReason: verdict.reason });\n }\n this.#asked.set(hookInput.tool_use_id, {\n ...(verdict.ruleId !== undefined && { ruleId: verdict.ruleId }),\n ...(verdict.reason !== undefined && { reason: verdict.reason }),\n });\n return decide('ask');\n };\n\n #canUseTool: CanUseTool = (toolName, input, { signal, toolUseID }) => {\n if (this.#closed) return Promise.resolve({ behavior: 'deny', message: 'Session is closed' });\n let label: { ruleId?: string; reason?: string } = {};\n if (this.#gate) {\n // Only calls the gate itself answered `ask` may prompt; anything else\n // reaching here bypassed the hook and is refused (defense in depth).\n // Session-scope grants live in the host's gateway, not here.\n const asked = this.#asked.get(toolUseID);\n if (!asked) {\n return Promise.resolve({\n behavior: 'deny',\n message: `Tool \"${toolName}\" was not cleared by the tool gateway`,\n });\n }\n this.#asked.delete(toolUseID);\n label = asked;\n } else {\n if (this.#sessionAllowed.has(toolName)) return Promise.resolve({ behavior: 'allow' });\n if (!this.#askTools.has(toolName)) {\n return Promise.resolve({\n behavior: 'deny',\n message: `Tool \"${toolName}\" is not permitted by this app`,\n });\n }\n }\n const approvalId = toolUseID || randomUUID();\n return new Promise<PermissionResult>((resolve) => {\n this.#pendingApprovals.set(approvalId, { tool: toolName, resolve });\n signal.addEventListener('abort', () => {\n this.#settleApproval(approvalId, { behavior: 'deny' }, 'session_closed');\n });\n this.#pushOutOfBand({\n type: 'approval_required',\n approvalId,\n tool: toolName,\n input,\n ...label,\n });\n });\n };\n\n async respondToApproval(approvalId: string, decision: ApprovalDecision): Promise<void> {\n if (!this.#pendingApprovals.has(approvalId)) {\n throw new AppSdkError(`Unknown or already resolved approval \"${approvalId}\"`);\n }\n const reason = decision.behavior === 'deny' ? (decision.reason ?? 'user') : 'user';\n this.#settleApproval(approvalId, decision, reason);\n }\n\n #settleApproval(\n approvalId: string,\n decision: ApprovalDecision,\n reason: 'user' | 'timeout' | 'session_closed',\n ): void {\n const pending = this.#pendingApprovals.get(approvalId);\n if (!pending) return;\n this.#pendingApprovals.delete(approvalId);\n if (decision.behavior === 'allow') {\n if (!this.#gate && (decision.scope === 'session' || decision.scope === 'always')) {\n this.#sessionAllowed.add(pending.tool);\n }\n pending.resolve({ behavior: 'allow' });\n } else {\n pending.resolve({ behavior: 'deny', message: decision.message ?? 'Denied by user' });\n }\n this.#pushOutOfBand({\n type: 'approval_resolved',\n approvalId,\n behavior: decision.behavior,\n reason,\n });\n }\n\n #pushOutOfBand(event: AgentEvent): void {\n this.#outOfBand.push(event);\n this.#wakeOutOfBand?.();\n }\n\n #denyAllPending(): void {\n for (const approvalId of [...this.#pendingApprovals.keys()]) {\n this.#settleApproval(approvalId, { behavior: 'deny' }, 'session_closed');\n }\n }\n\n async *#run(input: string): AsyncGenerator<AgentEvent> {\n try {\n this.#queue.push({\n type: 'user',\n message: { role: 'user', content: input },\n parent_tool_use_id: null,\n });\n while (true) {\n yield* this.#outOfBand.splice(0);\n this.#sdkNext ??= this.#iterator.next();\n const wake = new Promise<void>((resolve) => {\n this.#wakeOutOfBand = resolve;\n });\n // An event pushed while the generator was suspended in yield* above\n // would have woken a stale waker; re-check before racing.\n if (this.#outOfBand.length > 0) continue;\n const winner = await Promise.race([\n this.#sdkNext.then((result) => ({ sdk: result })),\n wake.then(() => undefined),\n ]);\n if (winner === undefined) continue; // out-of-band event arrived; drain at loop top\n this.#sdkNext = undefined;\n const next = winner.sdk;\n if (next.done) {\n yield { type: 'result', ok: false, error: 'Session ended unexpectedly' };\n return;\n }\n const message = next.value;\n for (const event of mapMessage(message, this.#tally)) {\n if (event.type === 'session') this.#id = event.sessionId;\n yield event;\n }\n if (message.type === 'result') {\n yield* this.#outOfBand.splice(0);\n return;\n }\n }\n } finally {\n this.#wakeOutOfBand = undefined;\n this.#busy = false;\n }\n }\n\n async interrupt(): Promise<void> {\n this.#denyAllPending();\n await this.#query.interrupt();\n }\n\n async close(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n this.#denyAllPending();\n this.#queue.end();\n await this.#iterator.return?.(undefined)?.catch(() => undefined);\n }\n}\n\nexport class ClaudeCodeProvider implements AIProvider {\n readonly name: string = 'claude-code';\n readonly supportsApprovals: boolean = true;\n readonly supportsToolGate: boolean = true;\n readonly #defaults: Partial<SessionOptions>;\n readonly #projectsDir: string | undefined;\n\n constructor(options: ClaudeCodeProviderOptions = {}) {\n this.#defaults = options.sessionDefaults ?? {};\n this.#projectsDir = options.claudeProjectsDir;\n }\n\n async createSession(options: SessionOptions = {}): Promise<AISession> {\n const merged = { ...this.#defaults, ...options };\n const skillsPluginDir = merged.skillDirs?.length\n ? await ensureSkillsPlugin(merged.skillDirs)\n : undefined;\n return new ClaudeSession(merged, skillsPluginDir);\n }\n\n async history(sessionId: string): Promise<ChatMessage[]> {\n return readSessionHistory(sessionId, this.#projectsDir ?? defaultProjectsDir());\n }\n\n /** Snapshots Claude Code's own JSONL transcripts; unknown ids are omitted. */\n async exportTranscripts(sessionIds: string[]): Promise<SessionTranscript[]> {\n return exportSessionTranscripts(sessionIds, this.#projectsDir ?? defaultProjectsDir());\n }\n\n /**\n * Restores transcripts into the project dir Claude Code derives from `cwd`\n * (the app's data dir on this machine). Existing transcripts are left alone.\n */\n async importTranscripts(\n transcripts: SessionTranscript[],\n options: { cwd: string },\n ): Promise<void> {\n return importSessionTranscripts(transcripts, {\n cwd: options.cwd,\n projectsDir: this.#projectsDir ?? defaultProjectsDir(),\n });\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alashi/provider-claude",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Claude Code AIProvider for alashi, built on the Claude Agent SDK",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,7 +24,7 @@
24
24
  "dist"
25
25
  ],
26
26
  "dependencies": {
27
- "@alashi/apps-sdk": "^0.9.0",
27
+ "@alashi/apps-sdk": "^0.11.0",
28
28
  "@anthropic-ai/claude-agent-sdk": "^0.3.232"
29
29
  },
30
30
  "devDependencies": {