@alashi/provider-claude 0.4.0 → 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
@@ -10,6 +10,7 @@ declare class ClaudeCodeProvider implements AIProvider {
10
10
  #private;
11
11
  readonly name: string;
12
12
  readonly supportsApprovals: boolean;
13
+ readonly supportsToolGate: boolean;
13
14
  constructor(options?: ClaudeCodeProviderOptions);
14
15
  createSession(options?: SessionOptions): Promise<AISession>;
15
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":";;UA0BiB;;EAEf,kBAAkB,QAAQ;;EAE1B;;cAqNW,8BAA8B;;WAChC;WACA;EAIT,YAAY,UAAS;EAKrB,cAAoB,UAAS,iBAAsB,QAAQ;EAQ3D,QAAc,oBAAoB,QAAQ;;EAK1C,kBAAwB,uBAAuB,QAAQ;;;;;EAQvD,kBACE,aAAa,qBACb;IAAW;MACV;;;;;;;;;;;;iBCzQW,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
@@ -353,7 +353,9 @@ async function importSessionTranscripts(transcripts, options) {
353
353
  }
354
354
  //#endregion
355
355
  //#region src/claude-provider.ts
356
- function toQueryOptions(options, skillsPluginDir, canUseTool) {
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) {
357
359
  const out = canUseTool ? {
358
360
  permissionMode: "default",
359
361
  canUseTool,
@@ -367,13 +369,14 @@ function toQueryOptions(options, skillsPluginDir, canUseTool) {
367
369
  if (options.systemPrompt !== void 0) out.systemPrompt = options.systemPrompt;
368
370
  if (options.model !== void 0) out.model = options.model;
369
371
  if (options.mcpServers !== void 0) out.mcpServers = options.mcpServers;
370
- 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;
371
374
  if (options.maxTurns !== void 0) out.maxTurns = options.maxTurns;
372
375
  if (options.cwd !== void 0) out.cwd = options.cwd;
373
376
  if (options.resume !== void 0) out.resume = options.resume;
374
377
  if (options.agents !== void 0 && Object.keys(options.agents).length > 0) {
375
378
  out.agents = options.agents;
376
- out.allowedTools = [.../* @__PURE__ */ new Set([...out.allowedTools ?? [], "Agent"])];
379
+ if (!gateHook) out.allowedTools = [.../* @__PURE__ */ new Set([...out.allowedTools ?? [], "Agent"])];
377
380
  }
378
381
  if (skillsPluginDir !== void 0) {
379
382
  out.plugins = [{
@@ -398,6 +401,8 @@ var ClaudeSession = class {
398
401
  #closed = false;
399
402
  #tally = new UsageTally();
400
403
  #askTools;
404
+ #gate;
405
+ #asked = /* @__PURE__ */ new Map();
401
406
  #sessionAllowed = /* @__PURE__ */ new Set();
402
407
  #pendingApprovals = /* @__PURE__ */ new Map();
403
408
  #outOfBand = [];
@@ -405,10 +410,11 @@ var ClaudeSession = class {
405
410
  #sdkNext;
406
411
  constructor(options, skillsPluginDir) {
407
412
  this.#askTools = new Set(options.askTools ?? []);
408
- const canUseTool = this.#askTools.size > 0 ? this.#canUseTool : void 0;
413
+ this.#gate = options.toolGate;
414
+ const canUseTool = this.#gate || this.#askTools.size > 0 ? this.#canUseTool : void 0;
409
415
  this.#query = query({
410
416
  prompt: this.#queue,
411
- options: toQueryOptions(options, skillsPluginDir, canUseTool)
417
+ options: toQueryOptions(options, skillsPluginDir, canUseTool, this.#gate ? this.#gateHook : void 0)
412
418
  });
413
419
  this.#iterator = this.#query[Symbol.asyncIterator]();
414
420
  }
@@ -421,16 +427,55 @@ var ClaudeSession = class {
421
427
  this.#busy = true;
422
428
  return this.#run(input);
423
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
+ };
424
458
  #canUseTool = (toolName, input, { signal, toolUseID }) => {
425
459
  if (this.#closed) return Promise.resolve({
426
460
  behavior: "deny",
427
461
  message: "Session is closed"
428
462
  });
429
- if (this.#sessionAllowed.has(toolName)) return Promise.resolve({ behavior: "allow" });
430
- if (!this.#askTools.has(toolName)) return Promise.resolve({
431
- behavior: "deny",
432
- message: `Tool "${toolName}" is not permitted by this app`
433
- });
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
+ }
434
479
  const approvalId = toolUseID || randomUUID();
435
480
  return new Promise((resolve) => {
436
481
  this.#pendingApprovals.set(approvalId, {
@@ -444,7 +489,8 @@ var ClaudeSession = class {
444
489
  type: "approval_required",
445
490
  approvalId,
446
491
  tool: toolName,
447
- input
492
+ input,
493
+ ...label
448
494
  });
449
495
  });
450
496
  };
@@ -458,7 +504,7 @@ var ClaudeSession = class {
458
504
  if (!pending) return;
459
505
  this.#pendingApprovals.delete(approvalId);
460
506
  if (decision.behavior === "allow") {
461
- if (decision.scope === "session" || decision.scope === "always") this.#sessionAllowed.add(pending.tool);
507
+ if (!this.#gate && (decision.scope === "session" || decision.scope === "always")) this.#sessionAllowed.add(pending.tool);
462
508
  pending.resolve({ behavior: "allow" });
463
509
  } else pending.resolve({
464
510
  behavior: "deny",
@@ -537,6 +583,7 @@ var ClaudeSession = class {
537
583
  var ClaudeCodeProvider = class {
538
584
  name = "claude-code";
539
585
  supportsApprovals = true;
586
+ supportsToolGate = true;
540
587
  #defaults;
541
588
  #projectsDir;
542
589
  constructor(options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["#costUsd","#models","#done","#resolvers","#values","#askTools","#canUseTool","#query","#queue","#iterator","#id","#closed","#busy","#run","#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} from '@alashi/apps-sdk';\nimport {\n type CanUseTool,\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\nfunction toQueryOptions(\n options: SessionOptions,\n skillsPluginDir?: string,\n canUseTool?: CanUseTool,\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 (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 #askTools: Set<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 const canUseTool = this.#askTools.size > 0 ? this.#canUseTool : undefined;\n this.#query = query({\n prompt: this.#queue,\n options: toQueryOptions(options, skillsPluginDir, canUseTool),\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 #canUseTool: CanUseTool = (toolName, input, { signal, toolUseID }) => {\n if (this.#closed) return Promise.resolve({ behavior: 'deny', message: 'Session is closed' });\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 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({ type: 'approval_required', approvalId, tool: toolName, input });\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 (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 #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.4.0",
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.10.0",
27
+ "@alashi/apps-sdk": "^0.11.0",
28
28
  "@anthropic-ai/claude-agent-sdk": "^0.3.232"
29
29
  },
30
30
  "devDependencies": {