@plannotator/pi-extension 0.15.0 → 0.15.2

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.
@@ -0,0 +1,106 @@
1
+ // @generated — DO NOT EDIT. Source: packages/ai/index.ts
2
+ /**
3
+ * @plannotator/ai — AI provider layer for Plannotator.
4
+ *
5
+ * This package provides the backbone for AI-powered features (inline chat,
6
+ * plan Q&A, code review assistance) across all Plannotator surfaces.
7
+ *
8
+ * Architecture:
9
+ *
10
+ * ┌─────────────────┐ ┌──────────────┐
11
+ * │ Plan Review UI │────▶│ │
12
+ * ├─────────────────┤ │ AI Endpoints │──▶ SSE stream
13
+ * │ Code Review UI │────▶│ (HTTP) │
14
+ * ├─────────────────┤ │ │
15
+ * │ Annotate UI │────▶└──────┬───────┘
16
+ * └─────────────────┘ │
17
+ * ▼
18
+ * ┌────────────────┐
19
+ * │ Session Manager │
20
+ * └────────┬───────┘
21
+ * │
22
+ * ┌────────▼───────┐
23
+ * │ AIProvider │ (abstract)
24
+ * └────────┬───────┘
25
+ * │
26
+ * ┌─────────────┼──────────────┐
27
+ * ▼ ▼ ▼
28
+ * ┌──────────────┐ ┌──────────┐ ┌───────────┐
29
+ * │ Claude Agent │ │ OpenCode │ │ Future │
30
+ * │ SDK Provider │ │ Provider │ │ Providers │
31
+ * └──────────────┘ └──────────┘ └───────────┘
32
+ *
33
+ * Quick start:
34
+ *
35
+ * ```ts
36
+ * import "@plannotator/ai/providers/claude-agent-sdk";
37
+ * import { ProviderRegistry, createProvider, createAIEndpoints, SessionManager } from "@plannotator/ai";
38
+ *
39
+ * // 1. Create a registry and provider
40
+ * const registry = new ProviderRegistry();
41
+ * const provider = await createProvider({ type: "claude-agent-sdk", cwd: process.cwd() });
42
+ * registry.register(provider);
43
+ *
44
+ * // 2. Create endpoints and session manager
45
+ * const sessionManager = new SessionManager();
46
+ * const aiEndpoints = createAIEndpoints({ registry, sessionManager });
47
+ *
48
+ * // 3. Mount endpoints in your Bun server
49
+ * // aiEndpoints["/api/ai/query"](request) → SSE Response
50
+ * ```
51
+ */
52
+
53
+ // Types
54
+ export type {
55
+ AIProvider,
56
+ AIProviderCapabilities,
57
+ AIProviderConfig,
58
+ AISession,
59
+ AIMessage,
60
+ AITextMessage,
61
+ AITextDeltaMessage,
62
+ AIToolUseMessage,
63
+ AIToolResultMessage,
64
+ AIErrorMessage,
65
+ AIResultMessage,
66
+ AIPermissionRequestMessage,
67
+ AIUnknownMessage,
68
+ AIContext,
69
+ AIContextMode,
70
+ PlanContext,
71
+ CodeReviewContext,
72
+ AnnotateContext,
73
+ ParentSession,
74
+ CreateSessionOptions,
75
+ ClaudeAgentSDKConfig,
76
+ CodexSDKConfig,
77
+ PiSDKConfig,
78
+ OpenCodeConfig,
79
+ } from "./types.ts";
80
+
81
+ // Provider registry
82
+ export {
83
+ ProviderRegistry,
84
+ registerProviderFactory,
85
+ createProvider,
86
+ } from "./provider.ts";
87
+
88
+ // Context builders
89
+ export { buildSystemPrompt, buildForkPreamble, buildEffectivePrompt } from "./context.ts";
90
+
91
+ // Base session
92
+ export { BaseSession } from "./base-session.ts";
93
+
94
+ // Session manager
95
+ export { SessionManager } from "./session-manager.ts";
96
+ export type { SessionEntry, SessionManagerOptions } from "./session-manager.ts";
97
+
98
+ // HTTP endpoints
99
+ export { createAIEndpoints } from "./endpoints.ts";
100
+ export type {
101
+ AIEndpoints,
102
+ AIEndpointDeps,
103
+ CreateSessionRequest,
104
+ QueryRequest,
105
+ AbortRequest,
106
+ } from "./endpoints.ts";
@@ -0,0 +1,104 @@
1
+ // @generated — DO NOT EDIT. Source: packages/ai/provider.ts
2
+ /**
3
+ * Provider registry — manages AI provider instances.
4
+ *
5
+ * Supports multiple instances of the same provider type (e.g., two Claude
6
+ * Agent SDK providers with different configs) keyed by instance ID.
7
+ *
8
+ * Each server (plan review, code review, annotate) should create its own
9
+ * ProviderRegistry or share one — no module-level global state.
10
+ */
11
+
12
+ import type { AIProvider, AIProviderConfig } from "./types.ts";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Factory registry (global — factories are stateless type→constructor maps)
16
+ // ---------------------------------------------------------------------------
17
+
18
+ type ProviderFactory = (config: AIProviderConfig) => Promise<AIProvider>;
19
+ const factories = new Map<string, ProviderFactory>();
20
+
21
+ /** Register a factory function for a provider type. */
22
+ export function registerProviderFactory(
23
+ type: string,
24
+ factory: ProviderFactory
25
+ ): void {
26
+ factories.set(type, factory);
27
+ }
28
+
29
+ /** Create a provider from config using a registered factory. Does NOT auto-register. */
30
+ export async function createProvider(
31
+ config: AIProviderConfig
32
+ ): Promise<AIProvider> {
33
+ const factory = factories.get(config.type);
34
+ if (!factory) {
35
+ throw new Error(
36
+ `No AI provider factory registered for type "${config.type}". ` +
37
+ `Available: ${[...factories.keys()].join(", ") || "(none)"}`
38
+ );
39
+ }
40
+ return factory(config);
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Registry
45
+ // ---------------------------------------------------------------------------
46
+
47
+ export class ProviderRegistry {
48
+ private instances = new Map<string, AIProvider>();
49
+
50
+ /**
51
+ * Register a provider instance under an ID.
52
+ * If no instanceId is provided, uses `provider.name`.
53
+ * Returns the instanceId used.
54
+ */
55
+ register(provider: AIProvider, instanceId?: string): string {
56
+ const id = instanceId ?? provider.name;
57
+ this.instances.set(id, provider);
58
+ return id;
59
+ }
60
+
61
+ /** Get a provider by instance ID. */
62
+ get(instanceId: string): AIProvider | undefined {
63
+ return this.instances.get(instanceId);
64
+ }
65
+
66
+ /** Get the first registered provider (convenience for single-provider setups). */
67
+ getDefault(): { id: string; provider: AIProvider } | undefined {
68
+ const first = this.instances.entries().next();
69
+ if (first.done) return undefined;
70
+ return { id: first.value[0], provider: first.value[1] };
71
+ }
72
+
73
+ /** Get all instances of a given provider type (by provider.name). */
74
+ getByType(typeName: string): AIProvider[] {
75
+ return [...this.instances.values()].filter((p) => p.name === typeName);
76
+ }
77
+
78
+ /** List all instance IDs. */
79
+ list(): string[] {
80
+ return [...this.instances.keys()];
81
+ }
82
+
83
+ /** Dispose and remove a single instance. No-op if not found. */
84
+ dispose(instanceId: string): void {
85
+ const provider = this.instances.get(instanceId);
86
+ if (provider) {
87
+ provider.dispose();
88
+ this.instances.delete(instanceId);
89
+ }
90
+ }
91
+
92
+ /** Dispose all providers and clear the registry. */
93
+ disposeAll(): void {
94
+ for (const provider of this.instances.values()) {
95
+ provider.dispose();
96
+ }
97
+ this.instances.clear();
98
+ }
99
+
100
+ /** Number of registered instances. */
101
+ get size(): number {
102
+ return this.instances.size;
103
+ }
104
+ }
@@ -0,0 +1,441 @@
1
+ // @generated — DO NOT EDIT. Source: packages/ai/providers/claude-agent-sdk.ts
2
+ /**
3
+ * Claude Agent SDK provider — the first concrete AIProvider implementation.
4
+ *
5
+ * Uses @anthropic-ai/claude-agent-sdk to create sessions that can:
6
+ * - Start fresh with Plannotator context as the system prompt
7
+ * - Fork from a parent Claude Code session (preserving full history)
8
+ * - Resume a previous Plannotator inline chat session
9
+ * - Stream text deltas back to the UI in real time
10
+ *
11
+ * Sessions are read-only by default (tools limited to Read, Glob, Grep)
12
+ * to keep inline chat safe and cost-bounded.
13
+ */
14
+
15
+ import { buildSystemPrompt, buildForkPreamble, buildEffectivePrompt } from "../context.ts";
16
+ import { BaseSession } from "../base-session.ts";
17
+ import type {
18
+ AIProvider,
19
+ AIProviderCapabilities,
20
+ AISession,
21
+ AIMessage,
22
+ CreateSessionOptions,
23
+ ClaudeAgentSDKConfig,
24
+ } from "../types.ts";
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Constants
28
+ // ---------------------------------------------------------------------------
29
+
30
+ const PROVIDER_NAME = "claude-agent-sdk";
31
+
32
+ /** Default read-only tools for inline chat. */
33
+ const DEFAULT_ALLOWED_TOOLS = ["Read", "Glob", "Grep", "WebSearch"];
34
+
35
+ const DEFAULT_MAX_TURNS = 99;
36
+ const DEFAULT_MODEL = "claude-sonnet-4-6";
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // SDK query options — typed to catch typos at compile time
40
+ // ---------------------------------------------------------------------------
41
+
42
+ interface ClaudeSDKQueryOptions {
43
+ model: string;
44
+ maxTurns: number;
45
+ allowedTools: string[];
46
+ cwd: string;
47
+ abortController: AbortController;
48
+ includePartialMessages: boolean;
49
+ persistSession: boolean;
50
+ maxBudgetUsd?: number;
51
+ systemPrompt?: string | { type: "preset"; preset: string; append?: string };
52
+ resume?: string;
53
+ forkSession?: boolean;
54
+ permissionMode?: ClaudeAgentSDKConfig['permissionMode'];
55
+ allowDangerouslySkipPermissions?: boolean;
56
+ pathToClaudeCodeExecutable?: string;
57
+ settingSources?: string[];
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Provider
62
+ // ---------------------------------------------------------------------------
63
+
64
+ export class ClaudeAgentSDKProvider implements AIProvider {
65
+ readonly name = PROVIDER_NAME;
66
+ readonly capabilities: AIProviderCapabilities = {
67
+ fork: true,
68
+ resume: true,
69
+ streaming: true,
70
+ tools: true,
71
+ };
72
+ readonly models = [
73
+ { id: 'claude-sonnet-4-6', label: 'Sonnet 4.6', default: true },
74
+ { id: 'claude-opus-4-6', label: 'Opus 4.6' },
75
+ { id: 'claude-haiku-4-5', label: 'Haiku 4.5' },
76
+ ] as const;
77
+
78
+ private config: ClaudeAgentSDKConfig;
79
+
80
+ constructor(config: ClaudeAgentSDKConfig) {
81
+ this.config = config;
82
+ }
83
+
84
+ async createSession(options: CreateSessionOptions): Promise<AISession> {
85
+ return new ClaudeAgentSDKSession({
86
+ ...this.baseConfig(options),
87
+ systemPrompt: buildSystemPrompt(options.context),
88
+ cwd: options.cwd ?? this.config.cwd ?? process.cwd(),
89
+ parentSessionId: null,
90
+ forkFromSession: null,
91
+ });
92
+ }
93
+
94
+ async forkSession(options: CreateSessionOptions): Promise<AISession> {
95
+ const parent = options.context.parent;
96
+ if (!parent) {
97
+ throw new Error(
98
+ "Cannot fork: no parent session provided in context. " +
99
+ "Use createSession() for standalone sessions."
100
+ );
101
+ }
102
+
103
+ return new ClaudeAgentSDKSession({
104
+ ...this.baseConfig(options),
105
+ systemPrompt: null,
106
+ forkPreamble: buildForkPreamble(options.context),
107
+ cwd: parent.cwd,
108
+ parentSessionId: parent.sessionId,
109
+ forkFromSession: parent.sessionId,
110
+ });
111
+ }
112
+
113
+ async resumeSession(sessionId: string): Promise<AISession> {
114
+ return new ClaudeAgentSDKSession({
115
+ ...this.baseConfig(),
116
+ systemPrompt: null,
117
+ cwd: this.config.cwd ?? process.cwd(),
118
+ parentSessionId: null,
119
+ forkFromSession: null,
120
+ resumeSessionId: sessionId,
121
+ });
122
+ }
123
+
124
+ dispose(): void {
125
+ // No persistent resources to clean up
126
+ }
127
+
128
+ private baseConfig(options?: CreateSessionOptions) {
129
+ return {
130
+ model: options?.model ?? this.config.model ?? DEFAULT_MODEL,
131
+ maxTurns: options?.maxTurns ?? DEFAULT_MAX_TURNS,
132
+ maxBudgetUsd: options?.maxBudgetUsd,
133
+ allowedTools: this.config.allowedTools ?? DEFAULT_ALLOWED_TOOLS,
134
+ permissionMode: this.config.permissionMode ?? "default",
135
+ claudeExecutablePath: this.config.claudeExecutablePath,
136
+ settingSources: this.config.settingSources ?? ['user', 'project'],
137
+ };
138
+ }
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // SDK import cache — resolve once, reuse across all queries
143
+ // ---------------------------------------------------------------------------
144
+
145
+ // biome-ignore lint/suspicious/noExplicitAny: SDK types resolved at runtime via dynamic import
146
+ let sdkQueryFn: ((...args: any[]) => any) | null = null;
147
+
148
+ async function getSDKQuery() {
149
+ if (!sdkQueryFn) {
150
+ const sdk = await import("@anthropic-ai/claude-agent-sdk");
151
+ sdkQueryFn = sdk.query;
152
+ }
153
+ return sdkQueryFn!;
154
+ }
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Session
158
+ // ---------------------------------------------------------------------------
159
+
160
+ interface SessionConfig {
161
+ systemPrompt: string | null;
162
+ forkPreamble?: string;
163
+ model: string;
164
+ maxTurns: number;
165
+ maxBudgetUsd?: number;
166
+ allowedTools: string[];
167
+ permissionMode: ClaudeAgentSDKConfig['permissionMode'];
168
+ cwd: string;
169
+ parentSessionId: string | null;
170
+ forkFromSession: string | null;
171
+ resumeSessionId?: string;
172
+ claudeExecutablePath?: string;
173
+ settingSources?: string[];
174
+ }
175
+
176
+ class ClaudeAgentSDKSession extends BaseSession {
177
+ private config: SessionConfig;
178
+ /** Active Query object — needed to send control responses (permission decisions) */
179
+ private _activeQuery: { streamInput: (iter: AsyncIterable<unknown>) => Promise<void> } | null = null;
180
+
181
+ constructor(config: SessionConfig) {
182
+ super({
183
+ parentSessionId: config.parentSessionId,
184
+ initialId: config.resumeSessionId,
185
+ });
186
+ this.config = config;
187
+ }
188
+
189
+ async *query(prompt: string): AsyncIterable<AIMessage> {
190
+ const started = this.startQuery();
191
+ if (!started) { yield BaseSession.BUSY_ERROR; return; }
192
+ const { gen } = started;
193
+
194
+ try {
195
+ const queryFn = await getSDKQuery();
196
+
197
+ const queryPrompt = buildEffectivePrompt(
198
+ prompt,
199
+ this.config.forkPreamble ?? null,
200
+ this._firstQuerySent,
201
+ );
202
+ const options = this.buildQueryOptions();
203
+
204
+ const stream = queryFn({ prompt: queryPrompt, options }) as
205
+ AsyncIterable<Record<string, unknown>> & { streamInput: (iter: AsyncIterable<unknown>) => Promise<void> };
206
+ this._activeQuery = stream;
207
+
208
+ this._firstQuerySent = true;
209
+
210
+ for await (const message of stream) {
211
+ const mapped = mapSDKMessage(message);
212
+
213
+ // Capture the real session ID from the init message
214
+ if (
215
+ !this._resolvedId &&
216
+ "session_id" in message &&
217
+ typeof message.session_id === "string" &&
218
+ message.session_id
219
+ ) {
220
+ this.resolveId(message.session_id);
221
+ }
222
+
223
+ for (const msg of mapped) {
224
+ yield msg;
225
+ }
226
+ }
227
+ } catch (err) {
228
+ yield {
229
+ type: "error",
230
+ error: err instanceof Error ? err.message : String(err),
231
+ code: "provider_error",
232
+ };
233
+ } finally {
234
+ this.endQuery(gen);
235
+ this._activeQuery = null;
236
+ }
237
+ }
238
+
239
+ abort(): void {
240
+ this._activeQuery = null;
241
+ super.abort();
242
+ }
243
+
244
+ respondToPermission(requestId: string, allow: boolean, message?: string): void {
245
+ if (!this._activeQuery || !this._activeQuery.streamInput) return;
246
+
247
+ const response = allow
248
+ ? { type: 'control_response', response: { subtype: 'success', request_id: requestId, response: { behavior: 'allow' } } }
249
+ : { type: 'control_response', response: { subtype: 'success', request_id: requestId, response: { behavior: 'deny', message: message ?? 'User denied this action' } } };
250
+
251
+ this._activeQuery.streamInput(
252
+ (async function* () { yield response; })()
253
+ ).catch(() => {});
254
+ }
255
+
256
+ // -------------------------------------------------------------------------
257
+ // Internal
258
+ // -------------------------------------------------------------------------
259
+
260
+ private buildQueryOptions(): ClaudeSDKQueryOptions {
261
+ const opts: ClaudeSDKQueryOptions = {
262
+ model: this.config.model,
263
+ maxTurns: this.config.maxTurns,
264
+ allowedTools: this.config.allowedTools,
265
+ cwd: this.config.cwd,
266
+ abortController: this._currentAbort!,
267
+ includePartialMessages: true,
268
+ persistSession: true,
269
+ ...(this.config.claudeExecutablePath && {
270
+ pathToClaudeCodeExecutable: this.config.claudeExecutablePath,
271
+ }),
272
+ ...(this.config.settingSources && {
273
+ settingSources: this.config.settingSources,
274
+ }),
275
+ };
276
+
277
+ if (this.config.maxBudgetUsd) {
278
+ opts.maxBudgetUsd = this.config.maxBudgetUsd;
279
+ }
280
+
281
+ // After the first query resolves a real session ID, all subsequent
282
+ // queries must resume that session to continue the conversation.
283
+ if (this._resolvedId) {
284
+ opts.resume = this._resolvedId;
285
+ return this.applyPermissionMode(opts);
286
+ }
287
+
288
+ // First query: use Claude Code's built-in prompt with our context appended
289
+ if (this.config.systemPrompt) {
290
+ opts.systemPrompt = {
291
+ type: "preset",
292
+ preset: "claude_code",
293
+ append: this.config.systemPrompt,
294
+ };
295
+ }
296
+
297
+ if (this.config.forkFromSession) {
298
+ opts.resume = this.config.forkFromSession;
299
+ opts.forkSession = true;
300
+ }
301
+
302
+ if (this.config.resumeSessionId) {
303
+ opts.resume = this.config.resumeSessionId;
304
+ }
305
+
306
+ return this.applyPermissionMode(opts);
307
+ }
308
+
309
+ private applyPermissionMode(opts: ClaudeSDKQueryOptions): ClaudeSDKQueryOptions {
310
+ if (this.config.permissionMode === "bypassPermissions") {
311
+ opts.permissionMode = "bypassPermissions";
312
+ opts.allowDangerouslySkipPermissions = true;
313
+ } else if (this.config.permissionMode === "plan") {
314
+ opts.permissionMode = "plan";
315
+ }
316
+ return opts;
317
+ }
318
+ }
319
+
320
+ // ---------------------------------------------------------------------------
321
+ // Message mapping
322
+ // ---------------------------------------------------------------------------
323
+
324
+ /**
325
+ * Map an SDK message to one or more AIMessages.
326
+ *
327
+ * An SDK assistant message can contain both text and tool_use content blocks
328
+ * in a single response. We emit each block as a separate AIMessage so no
329
+ * content is dropped.
330
+ */
331
+ function mapSDKMessage(msg: Record<string, unknown>): AIMessage[] {
332
+ const type = msg.type as string;
333
+
334
+ switch (type) {
335
+ case "assistant": {
336
+ const message = msg.message as Record<string, unknown> | undefined;
337
+ if (!message) return [{ type: "unknown", raw: msg }];
338
+ const content = message.content as Array<Record<string, unknown>>;
339
+ if (!content) return [{ type: "unknown", raw: msg }];
340
+
341
+ const messages: AIMessage[] = [];
342
+ const textParts: string[] = [];
343
+
344
+ for (const block of content) {
345
+ if (block.type === "text" && typeof block.text === "string") {
346
+ textParts.push(block.text);
347
+ } else if (block.type === "tool_use") {
348
+ // Flush accumulated text before the tool_use block
349
+ if (textParts.length > 0) {
350
+ messages.push({ type: "text", text: textParts.join("") });
351
+ textParts.length = 0;
352
+ }
353
+ messages.push({
354
+ type: "tool_use",
355
+ toolName: block.name as string,
356
+ toolInput: block.input as Record<string, unknown>,
357
+ toolUseId: block.id as string,
358
+ });
359
+ }
360
+ }
361
+
362
+ // Flush any remaining text after the last block
363
+ if (textParts.length > 0) {
364
+ messages.push({ type: "text", text: textParts.join("") });
365
+ }
366
+
367
+ return messages.length > 0 ? messages : [{ type: "unknown", raw: msg }];
368
+ }
369
+
370
+ case "stream_event": {
371
+ const event = msg.event as Record<string, unknown> | undefined;
372
+ if (!event) return [{ type: "unknown", raw: msg }];
373
+ const eventType = event.type as string;
374
+
375
+ if (eventType === "content_block_delta") {
376
+ const delta = event.delta as Record<string, unknown>;
377
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
378
+ return [{ type: "text_delta", delta: delta.text }];
379
+ }
380
+ }
381
+ return [{ type: "unknown", raw: msg }];
382
+ }
383
+
384
+ case "user": {
385
+ // SDK wraps tool results in SDKUserMessage (type: "user")
386
+ if (msg.tool_use_result != null) {
387
+ return [{
388
+ type: "tool_result",
389
+ result: typeof msg.tool_use_result === "string"
390
+ ? msg.tool_use_result
391
+ : JSON.stringify(msg.tool_use_result),
392
+ }];
393
+ }
394
+ return [{ type: "unknown", raw: msg }];
395
+ }
396
+
397
+ case "control_request": {
398
+ const request = msg.request as Record<string, unknown> | undefined;
399
+ if (request?.subtype === "can_use_tool") {
400
+ return [{
401
+ type: "permission_request",
402
+ requestId: msg.request_id as string,
403
+ toolName: request.tool_name as string,
404
+ toolInput: (request.input as Record<string, unknown>) ?? {},
405
+ title: request.title as string | undefined,
406
+ displayName: request.display_name as string | undefined,
407
+ description: request.description as string | undefined,
408
+ toolUseId: request.tool_use_id as string,
409
+ }];
410
+ }
411
+ return [{ type: "unknown", raw: msg }];
412
+ }
413
+
414
+ case "result": {
415
+ const sessionId = (msg.session_id as string) ?? "";
416
+ const subtype = msg.subtype as string;
417
+ return [{
418
+ type: "result",
419
+ sessionId,
420
+ success: subtype === "success",
421
+ result: (msg.result as string) ?? undefined,
422
+ costUsd: msg.total_cost_usd as number | undefined,
423
+ turns: msg.num_turns as number | undefined,
424
+ }];
425
+ }
426
+
427
+ default:
428
+ return [{ type: "unknown", raw: msg }];
429
+ }
430
+ }
431
+
432
+ // ---------------------------------------------------------------------------
433
+ // Factory registration
434
+ // ---------------------------------------------------------------------------
435
+
436
+ import { registerProviderFactory } from "../provider.ts";
437
+
438
+ registerProviderFactory(
439
+ PROVIDER_NAME,
440
+ async (config) => new ClaudeAgentSDKProvider(config as ClaudeAgentSDKConfig)
441
+ );