@willh/subtitle-correction-agent 0.1.2 → 0.1.3

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.
Files changed (2) hide show
  1. package/dist/index.js +537 -84
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3272,21 +3272,81 @@ var require_main = __commonJS((exports) => {
3272
3272
  // src/index.ts
3273
3273
  import * as path6 from "path";
3274
3274
  import * as fs5 from "fs";
3275
- import { fileURLToPath } from "url";
3275
+ import { fileURLToPath as fileURLToPath2 } from "url";
3276
3276
  import { execSync } from "child_process";
3277
3277
 
3278
3278
  // node_modules/@github/copilot-sdk/dist/client.js
3279
- var import_node = __toESM(require_main(), 1);
3279
+ var import_node2 = __toESM(require_main(), 1);
3280
3280
  import { spawn } from "node:child_process";
3281
+ import { existsSync } from "node:fs";
3281
3282
  import { Socket } from "node:net";
3283
+ import { dirname, join } from "node:path";
3284
+ import { fileURLToPath } from "node:url";
3285
+
3286
+ // node_modules/@github/copilot-sdk/dist/generated/rpc.js
3287
+ function createServerRpc(connection) {
3288
+ return {
3289
+ ping: async (params) => connection.sendRequest("ping", params),
3290
+ models: {
3291
+ list: async () => connection.sendRequest("models.list", {})
3292
+ },
3293
+ tools: {
3294
+ list: async (params) => connection.sendRequest("tools.list", params)
3295
+ },
3296
+ account: {
3297
+ getQuota: async () => connection.sendRequest("account.getQuota", {})
3298
+ }
3299
+ };
3300
+ }
3301
+ function createSessionRpc(connection, sessionId) {
3302
+ return {
3303
+ model: {
3304
+ getCurrent: async () => connection.sendRequest("session.model.getCurrent", { sessionId }),
3305
+ switchTo: async (params) => connection.sendRequest("session.model.switchTo", { sessionId, ...params })
3306
+ },
3307
+ mode: {
3308
+ get: async () => connection.sendRequest("session.mode.get", { sessionId }),
3309
+ set: async (params) => connection.sendRequest("session.mode.set", { sessionId, ...params })
3310
+ },
3311
+ plan: {
3312
+ read: async () => connection.sendRequest("session.plan.read", { sessionId }),
3313
+ update: async (params) => connection.sendRequest("session.plan.update", { sessionId, ...params }),
3314
+ delete: async () => connection.sendRequest("session.plan.delete", { sessionId })
3315
+ },
3316
+ workspace: {
3317
+ listFiles: async () => connection.sendRequest("session.workspace.listFiles", { sessionId }),
3318
+ readFile: async (params) => connection.sendRequest("session.workspace.readFile", { sessionId, ...params }),
3319
+ createFile: async (params) => connection.sendRequest("session.workspace.createFile", { sessionId, ...params })
3320
+ },
3321
+ fleet: {
3322
+ start: async (params) => connection.sendRequest("session.fleet.start", { sessionId, ...params })
3323
+ },
3324
+ agent: {
3325
+ list: async () => connection.sendRequest("session.agent.list", { sessionId }),
3326
+ getCurrent: async () => connection.sendRequest("session.agent.getCurrent", { sessionId }),
3327
+ select: async (params) => connection.sendRequest("session.agent.select", { sessionId, ...params }),
3328
+ deselect: async () => connection.sendRequest("session.agent.deselect", { sessionId })
3329
+ },
3330
+ compaction: {
3331
+ compact: async () => connection.sendRequest("session.compaction.compact", { sessionId })
3332
+ },
3333
+ tools: {
3334
+ handlePendingToolCall: async (params) => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params })
3335
+ },
3336
+ permissions: {
3337
+ handlePendingPermissionRequest: async (params) => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params })
3338
+ }
3339
+ };
3340
+ }
3282
3341
 
3283
3342
  // node_modules/@github/copilot-sdk/dist/sdkProtocolVersion.js
3284
- var SDK_PROTOCOL_VERSION = 2;
3343
+ var SDK_PROTOCOL_VERSION = 3;
3285
3344
  function getSdkProtocolVersion() {
3286
3345
  return SDK_PROTOCOL_VERSION;
3287
3346
  }
3288
3347
 
3289
3348
  // node_modules/@github/copilot-sdk/dist/session.js
3349
+ var import_node = __toESM(require_main(), 1);
3290
3350
  class CopilotSession {
3291
3351
  constructor(sessionId, connection, _workspacePath) {
3292
3352
  this.sessionId = sessionId;
@@ -3294,8 +3354,18 @@ class CopilotSession {
3294
3354
  this._workspacePath = _workspacePath;
3295
3355
  }
3296
3356
  eventHandlers = /* @__PURE__ */ new Set;
3357
+ typedEventHandlers = /* @__PURE__ */ new Map;
3297
3358
  toolHandlers = /* @__PURE__ */ new Map;
3298
3359
  permissionHandler;
3360
+ userInputHandler;
3361
+ hooks;
3362
+ _rpc = null;
3363
+ get rpc() {
3364
+ if (!this._rpc) {
3365
+ this._rpc = createSessionRpc(this.connection, this.sessionId);
3366
+ }
3367
+ return this._rpc;
3368
+ }
3299
3369
  get workspacePath() {
3300
3370
  return this._workspacePath;
3301
3371
  }
@@ -3328,30 +3398,123 @@ class CopilotSession {
3328
3398
  rejectWithError(error);
3329
3399
  }
3330
3400
  });
3401
+ let timeoutId;
3331
3402
  try {
3332
3403
  await this.send(options);
3333
3404
  const timeoutPromise = new Promise((_, reject) => {
3334
- setTimeout(() => reject(new Error(`Timeout after ${effectiveTimeout}ms waiting for session.idle`)), effectiveTimeout);
3405
+ timeoutId = setTimeout(() => reject(new Error(`Timeout after ${effectiveTimeout}ms waiting for session.idle`)), effectiveTimeout);
3335
3406
  });
3336
3407
  await Promise.race([idlePromise, timeoutPromise]);
3337
3408
  return lastAssistantMessage;
3338
3409
  } finally {
3410
+ if (timeoutId !== undefined) {
3411
+ clearTimeout(timeoutId);
3412
+ }
3339
3413
  unsubscribe();
3340
3414
  }
3341
3415
  }
3342
- on(handler) {
3343
- this.eventHandlers.add(handler);
3416
+ on(eventTypeOrHandler, handler) {
3417
+ if (typeof eventTypeOrHandler === "string" && handler) {
3418
+ const eventType = eventTypeOrHandler;
3419
+ if (!this.typedEventHandlers.has(eventType)) {
3420
+ this.typedEventHandlers.set(eventType, /* @__PURE__ */ new Set);
3421
+ }
3422
+ const storedHandler = handler;
3423
+ this.typedEventHandlers.get(eventType).add(storedHandler);
3424
+ return () => {
3425
+ const handlers = this.typedEventHandlers.get(eventType);
3426
+ if (handlers) {
3427
+ handlers.delete(storedHandler);
3428
+ }
3429
+ };
3430
+ }
3431
+ const wildcardHandler = eventTypeOrHandler;
3432
+ this.eventHandlers.add(wildcardHandler);
3344
3433
  return () => {
3345
- this.eventHandlers.delete(handler);
3434
+ this.eventHandlers.delete(wildcardHandler);
3346
3435
  };
3347
3436
  }
3348
3437
  _dispatchEvent(event) {
3438
+ this._handleBroadcastEvent(event);
3439
+ const typedHandlers = this.typedEventHandlers.get(event.type);
3440
+ if (typedHandlers) {
3441
+ for (const handler of typedHandlers) {
3442
+ try {
3443
+ handler(event);
3444
+ } catch (_error) {}
3445
+ }
3446
+ }
3349
3447
  for (const handler of this.eventHandlers) {
3350
3448
  try {
3351
3449
  handler(event);
3352
3450
  } catch (_error) {}
3353
3451
  }
3354
3452
  }
3453
+ _handleBroadcastEvent(event) {
3454
+ if (event.type === "external_tool.requested") {
3455
+ const { requestId, toolName } = event.data;
3456
+ const args = event.data.arguments;
3457
+ const toolCallId = event.data.toolCallId;
3458
+ const handler = this.toolHandlers.get(toolName);
3459
+ if (handler) {
3460
+ this._executeToolAndRespond(requestId, toolName, toolCallId, args, handler);
3461
+ }
3462
+ } else if (event.type === "permission.requested") {
3463
+ const { requestId, permissionRequest } = event.data;
3464
+ if (this.permissionHandler) {
3465
+ this._executePermissionAndRespond(requestId, permissionRequest);
3466
+ }
3467
+ }
3468
+ }
3469
+ async _executeToolAndRespond(requestId, toolName, toolCallId, args, handler) {
3470
+ try {
3471
+ const rawResult = await handler(args, {
3472
+ sessionId: this.sessionId,
3473
+ toolCallId,
3474
+ toolName,
3475
+ arguments: args
3476
+ });
3477
+ let result;
3478
+ if (rawResult == null) {
3479
+ result = "";
3480
+ } else if (typeof rawResult === "string") {
3481
+ result = rawResult;
3482
+ } else {
3483
+ result = JSON.stringify(rawResult);
3484
+ }
3485
+ await this.rpc.tools.handlePendingToolCall({ requestId, result });
3486
+ } catch (error) {
3487
+ const message = error instanceof Error ? error.message : String(error);
3488
+ try {
3489
+ await this.rpc.tools.handlePendingToolCall({ requestId, error: message });
3490
+ } catch (rpcError) {
3491
+ if (!(rpcError instanceof import_node.ConnectionError || rpcError instanceof import_node.ResponseError)) {
3492
+ throw rpcError;
3493
+ }
3494
+ }
3495
+ }
3496
+ }
3497
+ async _executePermissionAndRespond(requestId, permissionRequest) {
3498
+ try {
3499
+ const result = await this.permissionHandler(permissionRequest, {
3500
+ sessionId: this.sessionId
3501
+ });
3502
+ await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result });
3503
+ } catch (_error) {
3504
+ try {
3505
+ await this.rpc.permissions.handlePendingPermissionRequest({
3506
+ requestId,
3507
+ result: {
3508
+ kind: "denied-no-approval-rule-and-could-not-request-from-user"
3509
+ }
3510
+ });
3511
+ } catch (rpcError) {
3512
+ if (!(rpcError instanceof import_node.ConnectionError || rpcError instanceof import_node.ResponseError)) {
3513
+ throw rpcError;
3514
+ }
3515
+ }
3516
+ }
3517
+ }
3355
3518
  registerTools(tools) {
3356
3519
  this.toolHandlers.clear();
3357
3520
  if (!tools) {
@@ -3367,7 +3530,13 @@ class CopilotSession {
3367
3530
  registerPermissionHandler(handler) {
3368
3531
  this.permissionHandler = handler;
3369
3532
  }
3370
- async _handlePermissionRequest(request) {
3533
+ registerUserInputHandler(handler) {
3534
+ this.userInputHandler = handler;
3535
+ }
3536
+ registerHooks(hooks) {
3537
+ this.hooks = hooks;
3538
+ }
3539
+ async _handlePermissionRequestV2(request) {
3371
3540
  if (!this.permissionHandler) {
3372
3541
  return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
3373
3542
  }
@@ -3380,28 +3549,75 @@ class CopilotSession {
3380
3549
  return { kind: "denied-no-approval-rule-and-could-not-request-from-user" };
3381
3550
  }
3382
3551
  }
3552
+ async _handleUserInputRequest(request) {
3553
+ if (!this.userInputHandler) {
3554
+ throw new Error("User input requested but no handler registered");
3555
+ }
3556
+ try {
3557
+ const result = await this.userInputHandler(request, {
3558
+ sessionId: this.sessionId
3559
+ });
3560
+ return result;
3561
+ } catch (error) {
3562
+ throw error;
3563
+ }
3564
+ }
3565
+ async _handleHooksInvoke(hookType, input) {
3566
+ if (!this.hooks) {
3567
+ return;
3568
+ }
3569
+ const handlerMap = {
3570
+ preToolUse: this.hooks.onPreToolUse,
3571
+ postToolUse: this.hooks.onPostToolUse,
3572
+ userPromptSubmitted: this.hooks.onUserPromptSubmitted,
3573
+ sessionStart: this.hooks.onSessionStart,
3574
+ sessionEnd: this.hooks.onSessionEnd,
3575
+ errorOccurred: this.hooks.onErrorOccurred
3576
+ };
3577
+ const handler = handlerMap[hookType];
3578
+ if (!handler) {
3579
+ return;
3580
+ }
3581
+ try {
3582
+ const result = await handler(input, { sessionId: this.sessionId });
3583
+ return result;
3584
+ } catch (_error) {
3585
+ return;
3586
+ }
3587
+ }
3383
3588
  async getMessages() {
3384
3589
  const response = await this.connection.sendRequest("session.getMessages", {
3385
3590
  sessionId: this.sessionId
3386
3591
  });
3387
3592
  return response.events;
3388
3593
  }
3389
- async destroy() {
3594
+ async disconnect() {
3390
3595
  await this.connection.sendRequest("session.destroy", {
3391
3596
  sessionId: this.sessionId
3392
3597
  });
3393
3598
  this.eventHandlers.clear();
3599
+ this.typedEventHandlers.clear();
3394
3600
  this.toolHandlers.clear();
3395
3601
  this.permissionHandler = undefined;
3396
3602
  }
3603
+ async destroy() {
3604
+ return this.disconnect();
3605
+ }
3606
+ async[Symbol.asyncDispose]() {
3607
+ return this.disconnect();
3608
+ }
3397
3609
  async abort() {
3398
3610
  await this.connection.sendRequest("session.abort", {
3399
3611
  sessionId: this.sessionId
3400
3612
  });
3401
3613
  }
3614
+ async setModel(model) {
3615
+ await this.rpc.model.switchTo({ modelId: model });
3616
+ }
3402
3617
  }
3403
3618
 
3404
3619
  // node_modules/@github/copilot-sdk/dist/client.js
3620
+ var MIN_PROTOCOL_VERSION = 2;
3405
3621
  function isZodSchema(value) {
3406
3622
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
3407
3623
  }
@@ -3413,6 +3629,17 @@ function toJsonSchema(parameters) {
3413
3629
  }
3414
3630
  return parameters;
3415
3631
  }
3632
+ function getNodeExecPath() {
3633
+ if (process.versions.bun) {
3634
+ return "node";
3635
+ }
3636
+ return process.execPath;
3637
+ }
3638
+ function getBundledCliPath() {
3639
+ const sdkUrl = import.meta.resolve("@github/copilot/sdk");
3640
+ const sdkPath = fileURLToPath(sdkUrl);
3641
+ return join(dirname(dirname(sdkPath)), "index.js");
3642
+ }
3416
3643
 
3417
3644
  class CopilotClient {
3418
3645
  cliProcess = null;
@@ -3422,30 +3649,59 @@ class CopilotClient {
3422
3649
  actualHost = "localhost";
3423
3650
  state = "disconnected";
3424
3651
  sessions = /* @__PURE__ */ new Map;
3652
+ stderrBuffer = "";
3425
3653
  options;
3426
3654
  isExternalServer = false;
3427
3655
  forceStopping = false;
3656
+ modelsCache = null;
3657
+ modelsCacheLock = Promise.resolve();
3658
+ sessionLifecycleHandlers = /* @__PURE__ */ new Set;
3659
+ typedLifecycleHandlers = /* @__PURE__ */ new Map;
3660
+ _rpc = null;
3661
+ processExitPromise = null;
3662
+ negotiatedProtocolVersion = null;
3663
+ get rpc() {
3664
+ if (!this.connection) {
3665
+ throw new Error("Client is not connected. Call start() first.");
3666
+ }
3667
+ if (!this._rpc) {
3668
+ this._rpc = createServerRpc(this.connection);
3669
+ }
3670
+ return this._rpc;
3671
+ }
3428
3672
  constructor(options = {}) {
3429
3673
  if (options.cliUrl && (options.useStdio === true || options.cliPath)) {
3430
3674
  throw new Error("cliUrl is mutually exclusive with useStdio and cliPath");
3431
3675
  }
3676
+ if (options.isChildProcess && (options.cliUrl || options.useStdio === false)) {
3677
+ throw new Error("isChildProcess must be used in conjunction with useStdio and not with cliUrl");
3678
+ }
3679
+ if (options.cliUrl && (options.githubToken || options.useLoggedInUser !== undefined)) {
3680
+ throw new Error("githubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)");
3681
+ }
3432
3682
  if (options.cliUrl) {
3433
3683
  const { host, port } = this.parseCliUrl(options.cliUrl);
3434
3684
  this.actualHost = host;
3435
3685
  this.actualPort = port;
3436
3686
  this.isExternalServer = true;
3437
3687
  }
3688
+ if (options.isChildProcess) {
3689
+ this.isExternalServer = true;
3690
+ }
3438
3691
  this.options = {
3439
- cliPath: options.cliPath || "copilot",
3692
+ cliPath: options.cliPath || getBundledCliPath(),
3440
3693
  cliArgs: options.cliArgs ?? [],
3441
3694
  cwd: options.cwd ?? process.cwd(),
3442
3695
  port: options.port || 0,
3443
3696
  useStdio: options.cliUrl ? false : options.useStdio ?? true,
3697
+ isChildProcess: options.isChildProcess ?? false,
3444
3698
  cliUrl: options.cliUrl,
3445
3699
  logLevel: options.logLevel || "debug",
3446
3700
  autoStart: options.autoStart ?? true,
3447
3701
  autoRestart: options.autoRestart ?? true,
3448
- env: options.env ?? process.env
3702
+ env: options.env ?? process.env,
3703
+ githubToken: options.githubToken,
3704
+ useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true)
3449
3705
  };
3450
3706
  }
3451
3707
  parseCliUrl(url) {
@@ -3488,7 +3744,7 @@ class CopilotClient {
3488
3744
  let lastError = null;
3489
3745
  for (let attempt = 1;attempt <= 3; attempt++) {
3490
3746
  try {
3491
- await session.destroy();
3747
+ await session.disconnect();
3492
3748
  lastError = null;
3493
3749
  break;
3494
3750
  } catch (error) {
@@ -3500,7 +3756,7 @@ class CopilotClient {
3500
3756
  }
3501
3757
  }
3502
3758
  if (lastError) {
3503
- errors.push(new Error(`Failed to destroy session ${sessionId} after 3 attempts: ${lastError.message}`));
3759
+ errors.push(new Error(`Failed to disconnect session ${sessionId} after 3 attempts: ${lastError.message}`));
3504
3760
  }
3505
3761
  }
3506
3762
  this.sessions.clear();
@@ -3511,7 +3767,9 @@ class CopilotClient {
3511
3767
  errors.push(new Error(`Failed to dispose connection: ${error instanceof Error ? error.message : String(error)}`));
3512
3768
  }
3513
3769
  this.connection = null;
3770
+ this._rpc = null;
3514
3771
  }
3772
+ this.modelsCache = null;
3515
3773
  if (this.socket) {
3516
3774
  try {
3517
3775
  this.socket.end();
@@ -3530,6 +3788,8 @@ class CopilotClient {
3530
3788
  }
3531
3789
  this.state = "disconnected";
3532
3790
  this.actualPort = null;
3791
+ this.stderrBuffer = "";
3792
+ this.processExitPromise = null;
3533
3793
  return errors;
3534
3794
  }
3535
3795
  async forceStop() {
@@ -3540,7 +3800,9 @@ class CopilotClient {
3540
3800
  this.connection.dispose();
3541
3801
  } catch {}
3542
3802
  this.connection = null;
3803
+ this._rpc = null;
3543
3804
  }
3805
+ this.modelsCache = null;
3544
3806
  if (this.socket) {
3545
3807
  try {
3546
3808
  this.socket.destroy();
@@ -3555,8 +3817,13 @@ class CopilotClient {
3555
3817
  }
3556
3818
  this.state = "disconnected";
3557
3819
  this.actualPort = null;
3820
+ this.stderrBuffer = "";
3821
+ this.processExitPromise = null;
3558
3822
  }
3559
- async createSession(config = {}) {
3823
+ async createSession(config) {
3824
+ if (!config?.onPermissionRequest) {
3825
+ throw new Error("An onPermissionRequest handler is required when creating a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }.");
3826
+ }
3560
3827
  if (!this.connection) {
3561
3828
  if (this.options.autoStart) {
3562
3829
  await this.start();
@@ -3567,18 +3834,25 @@ class CopilotClient {
3567
3834
  const response = await this.connection.sendRequest("session.create", {
3568
3835
  model: config.model,
3569
3836
  sessionId: config.sessionId,
3837
+ clientName: config.clientName,
3838
+ reasoningEffort: config.reasoningEffort,
3570
3839
  tools: config.tools?.map((tool) => ({
3571
3840
  name: tool.name,
3572
3841
  description: tool.description,
3573
- parameters: toJsonSchema(tool.parameters)
3842
+ parameters: toJsonSchema(tool.parameters),
3843
+ overridesBuiltInTool: tool.overridesBuiltInTool
3574
3844
  })),
3575
3845
  systemMessage: config.systemMessage,
3576
3846
  availableTools: config.availableTools,
3577
3847
  excludedTools: config.excludedTools,
3578
3848
  provider: config.provider,
3579
- requestPermission: !!config.onPermissionRequest,
3849
+ requestPermission: true,
3850
+ requestUserInput: !!config.onUserInputRequest,
3851
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
3852
+ workingDirectory: config.workingDirectory,
3580
3853
  streaming: config.streaming,
3581
3854
  mcpServers: config.mcpServers,
3855
+ envValueMode: "direct",
3582
3856
  customAgents: config.customAgents,
3583
3857
  configDir: config.configDir,
3584
3858
  skillDirectories: config.skillDirectories,
@@ -3588,13 +3862,20 @@ class CopilotClient {
3588
3862
  const { sessionId, workspacePath } = response;
3589
3863
  const session = new CopilotSession(sessionId, this.connection, workspacePath);
3590
3864
  session.registerTools(config.tools);
3591
- if (config.onPermissionRequest) {
3592
- session.registerPermissionHandler(config.onPermissionRequest);
3865
+ session.registerPermissionHandler(config.onPermissionRequest);
3866
+ if (config.onUserInputRequest) {
3867
+ session.registerUserInputHandler(config.onUserInputRequest);
3868
+ }
3869
+ if (config.hooks) {
3870
+ session.registerHooks(config.hooks);
3593
3871
  }
3594
3872
  this.sessions.set(sessionId, session);
3595
3873
  return session;
3596
3874
  }
3597
- async resumeSession(sessionId, config = {}) {
3875
+ async resumeSession(sessionId, config) {
3876
+ if (!config?.onPermissionRequest) {
3877
+ throw new Error("An onPermissionRequest handler is required when resuming a session. For example, to allow all permissions, use { onPermissionRequest: approveAll }.");
3878
+ }
3598
3879
  if (!this.connection) {
3599
3880
  if (this.options.autoStart) {
3600
3881
  await this.start();
@@ -3604,24 +3885,42 @@ class CopilotClient {
3604
3885
  }
3605
3886
  const response = await this.connection.sendRequest("session.resume", {
3606
3887
  sessionId,
3888
+ clientName: config.clientName,
3889
+ model: config.model,
3890
+ reasoningEffort: config.reasoningEffort,
3891
+ systemMessage: config.systemMessage,
3892
+ availableTools: config.availableTools,
3893
+ excludedTools: config.excludedTools,
3607
3894
  tools: config.tools?.map((tool) => ({
3608
3895
  name: tool.name,
3609
3896
  description: tool.description,
3610
- parameters: toJsonSchema(tool.parameters)
3897
+ parameters: toJsonSchema(tool.parameters),
3898
+ overridesBuiltInTool: tool.overridesBuiltInTool
3611
3899
  })),
3612
3900
  provider: config.provider,
3613
- requestPermission: !!config.onPermissionRequest,
3901
+ requestPermission: true,
3902
+ requestUserInput: !!config.onUserInputRequest,
3903
+ hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)),
3904
+ workingDirectory: config.workingDirectory,
3905
+ configDir: config.configDir,
3614
3906
  streaming: config.streaming,
3615
3907
  mcpServers: config.mcpServers,
3908
+ envValueMode: "direct",
3616
3909
  customAgents: config.customAgents,
3617
3910
  skillDirectories: config.skillDirectories,
3618
- disabledSkills: config.disabledSkills
3911
+ disabledSkills: config.disabledSkills,
3912
+ infiniteSessions: config.infiniteSessions,
3913
+ disableResume: config.disableResume
3619
3914
  });
3620
3915
  const { sessionId: resumedSessionId, workspacePath } = response;
3621
3916
  const session = new CopilotSession(resumedSessionId, this.connection, workspacePath);
3622
3917
  session.registerTools(config.tools);
3623
- if (config.onPermissionRequest) {
3624
- session.registerPermissionHandler(config.onPermissionRequest);
3918
+ session.registerPermissionHandler(config.onPermissionRequest);
3919
+ if (config.onUserInputRequest) {
3920
+ session.registerUserInputHandler(config.onUserInputRequest);
3921
+ }
3922
+ if (config.hooks) {
3923
+ session.registerHooks(config.hooks);
3625
3924
  }
3626
3925
  this.sessions.set(resumedSessionId, session);
3627
3926
  return session;
@@ -3654,20 +3953,40 @@ class CopilotClient {
3654
3953
  if (!this.connection) {
3655
3954
  throw new Error("Client not connected");
3656
3955
  }
3657
- const result = await this.connection.sendRequest("models.list", {});
3658
- const response = result;
3659
- return response.models;
3956
+ await this.modelsCacheLock;
3957
+ let resolveLock;
3958
+ this.modelsCacheLock = new Promise((resolve) => {
3959
+ resolveLock = resolve;
3960
+ });
3961
+ try {
3962
+ if (this.modelsCache !== null) {
3963
+ return [...this.modelsCache];
3964
+ }
3965
+ const result = await this.connection.sendRequest("models.list", {});
3966
+ const response = result;
3967
+ const models = response.models;
3968
+ this.modelsCache = models;
3969
+ return [...models];
3970
+ } finally {
3971
+ resolveLock();
3972
+ }
3660
3973
  }
3661
3974
  async verifyProtocolVersion() {
3662
- const expectedVersion = getSdkProtocolVersion();
3663
- const pingResult = await this.ping();
3975
+ const maxVersion = getSdkProtocolVersion();
3976
+ let pingResult;
3977
+ if (this.processExitPromise) {
3978
+ pingResult = await Promise.race([this.ping(), this.processExitPromise]);
3979
+ } else {
3980
+ pingResult = await this.ping();
3981
+ }
3664
3982
  const serverVersion = pingResult.protocolVersion;
3665
3983
  if (serverVersion === undefined) {
3666
- throw new Error(`SDK protocol version mismatch: SDK expects version ${expectedVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`);
3984
+ throw new Error(`SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`);
3667
3985
  }
3668
- if (serverVersion !== expectedVersion) {
3669
- throw new Error(`SDK protocol version mismatch: SDK expects version ${expectedVersion}, but server reports version ${serverVersion}. Please update your SDK or server to ensure compatibility.`);
3986
+ if (serverVersion < MIN_PROTOCOL_VERSION || serverVersion > maxVersion) {
3987
+ throw new Error(`SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server reports version ${serverVersion}. Please update your SDK or server to ensure compatibility.`);
3670
3988
  }
3989
+ this.negotiatedProtocolVersion = serverVersion;
3671
3990
  }
3672
3991
  async getLastSessionId() {
3673
3992
  if (!this.connection) {
@@ -3689,25 +4008,66 @@ class CopilotClient {
3689
4008
  }
3690
4009
  this.sessions.delete(sessionId);
3691
4010
  }
3692
- async listSessions() {
4011
+ async listSessions(filter) {
3693
4012
  if (!this.connection) {
3694
4013
  throw new Error("Client not connected");
3695
4014
  }
3696
- const response = await this.connection.sendRequest("session.list", {});
4015
+ const response = await this.connection.sendRequest("session.list", { filter });
3697
4016
  const { sessions } = response;
3698
4017
  return sessions.map((s) => ({
3699
4018
  sessionId: s.sessionId,
3700
4019
  startTime: new Date(s.startTime),
3701
4020
  modifiedTime: new Date(s.modifiedTime),
3702
4021
  summary: s.summary,
3703
- isRemote: s.isRemote
4022
+ isRemote: s.isRemote,
4023
+ context: s.context
3704
4024
  }));
3705
4025
  }
4026
+ async getForegroundSessionId() {
4027
+ if (!this.connection) {
4028
+ throw new Error("Client not connected");
4029
+ }
4030
+ const response = await this.connection.sendRequest("session.getForeground", {});
4031
+ return response.sessionId;
4032
+ }
4033
+ async setForegroundSessionId(sessionId) {
4034
+ if (!this.connection) {
4035
+ throw new Error("Client not connected");
4036
+ }
4037
+ const response = await this.connection.sendRequest("session.setForeground", { sessionId });
4038
+ const result = response;
4039
+ if (!result.success) {
4040
+ throw new Error(result.error || "Failed to set foreground session");
4041
+ }
4042
+ }
4043
+ on(eventTypeOrHandler, handler) {
4044
+ if (typeof eventTypeOrHandler === "string" && handler) {
4045
+ const eventType = eventTypeOrHandler;
4046
+ if (!this.typedLifecycleHandlers.has(eventType)) {
4047
+ this.typedLifecycleHandlers.set(eventType, /* @__PURE__ */ new Set);
4048
+ }
4049
+ const storedHandler = handler;
4050
+ this.typedLifecycleHandlers.get(eventType).add(storedHandler);
4051
+ return () => {
4052
+ const handlers = this.typedLifecycleHandlers.get(eventType);
4053
+ if (handlers) {
4054
+ handlers.delete(storedHandler);
4055
+ }
4056
+ };
4057
+ }
4058
+ const wildcardHandler = eventTypeOrHandler;
4059
+ this.sessionLifecycleHandlers.add(wildcardHandler);
4060
+ return () => {
4061
+ this.sessionLifecycleHandlers.delete(wildcardHandler);
4062
+ };
4063
+ }
3706
4064
  async startCLIServer() {
3707
4065
  return new Promise((resolve, reject) => {
4066
+ this.stderrBuffer = "";
3708
4067
  const args = [
3709
4068
  ...this.options.cliArgs,
3710
- "--server",
4069
+ "--headless",
4070
+ "--no-auto-update",
3711
4071
  "--log-level",
3712
4072
  this.options.logLevel
3713
4073
  ];
@@ -3716,27 +4076,37 @@ class CopilotClient {
3716
4076
  } else if (this.options.port > 0) {
3717
4077
  args.push("--port", this.options.port.toString());
3718
4078
  }
4079
+ if (this.options.githubToken) {
4080
+ args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN");
4081
+ }
4082
+ if (!this.options.useLoggedInUser) {
4083
+ args.push("--no-auto-login");
4084
+ }
3719
4085
  const envWithoutNodeDebug = { ...this.options.env };
3720
4086
  delete envWithoutNodeDebug.NODE_DEBUG;
4087
+ if (this.options.githubToken) {
4088
+ envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken;
4089
+ }
4090
+ if (!existsSync(this.options.cliPath)) {
4091
+ throw new Error(`Copilot CLI not found at ${this.options.cliPath}. Ensure @github/copilot is installed.`);
4092
+ }
4093
+ const stdioConfig = this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"];
3721
4094
  const isJsFile = this.options.cliPath.endsWith(".js");
3722
- const isAbsolutePath = this.options.cliPath.startsWith("/") || /^[a-zA-Z]:/.test(this.options.cliPath);
3723
- let command;
3724
- let spawnArgs;
3725
4095
  if (isJsFile) {
3726
- command = "node";
3727
- spawnArgs = [this.options.cliPath, ...args];
3728
- } else if (process.platform === "win32" && !isAbsolutePath) {
3729
- command = "cmd";
3730
- spawnArgs = ["/c", `${this.options.cliPath}`, ...args];
4096
+ this.cliProcess = spawn(getNodeExecPath(), [this.options.cliPath, ...args], {
4097
+ stdio: stdioConfig,
4098
+ cwd: this.options.cwd,
4099
+ env: envWithoutNodeDebug,
4100
+ windowsHide: true
4101
+ });
3731
4102
  } else {
3732
- command = this.options.cliPath;
3733
- spawnArgs = args;
4103
+ this.cliProcess = spawn(this.options.cliPath, args, {
4104
+ stdio: stdioConfig,
4105
+ cwd: this.options.cwd,
4106
+ env: envWithoutNodeDebug,
4107
+ windowsHide: true
4108
+ });
3734
4109
  }
3735
- this.cliProcess = spawn(command, spawnArgs, {
3736
- stdio: this.options.useStdio ? ["pipe", "pipe", "pipe"] : ["ignore", "pipe", "pipe"],
3737
- cwd: this.options.cwd,
3738
- env: envWithoutNodeDebug
3739
- });
3740
4110
  let stdout = "";
3741
4111
  let resolved = false;
3742
4112
  if (this.options.useStdio) {
@@ -3754,6 +4124,7 @@ class CopilotClient {
3754
4124
  });
3755
4125
  }
3756
4126
  this.cliProcess.stderr?.on("data", (data) => {
4127
+ this.stderrBuffer += data.toString();
3757
4128
  const lines = data.toString().split(`
3758
4129
  `);
3759
4130
  for (const line of lines) {
@@ -3766,13 +4137,39 @@ class CopilotClient {
3766
4137
  this.cliProcess.on("error", (error) => {
3767
4138
  if (!resolved) {
3768
4139
  resolved = true;
3769
- reject(new Error(`Failed to start CLI server: ${error.message}`));
4140
+ const stderrOutput = this.stderrBuffer.trim();
4141
+ if (stderrOutput) {
4142
+ reject(new Error(`Failed to start CLI server: ${error.message}
4143
+ stderr: ${stderrOutput}`));
4144
+ } else {
4145
+ reject(new Error(`Failed to start CLI server: ${error.message}`));
4146
+ }
3770
4147
  }
3771
4148
  });
4149
+ this.processExitPromise = new Promise((_, rejectProcessExit) => {
4150
+ this.cliProcess.on("exit", (code) => {
4151
+ setTimeout(() => {
4152
+ const stderrOutput = this.stderrBuffer.trim();
4153
+ if (stderrOutput) {
4154
+ rejectProcessExit(new Error(`CLI server exited with code ${code}
4155
+ stderr: ${stderrOutput}`));
4156
+ } else {
4157
+ rejectProcessExit(new Error(`CLI server exited unexpectedly with code ${code}`));
4158
+ }
4159
+ }, 50);
4160
+ });
4161
+ });
4162
+ this.processExitPromise.catch(() => {});
3772
4163
  this.cliProcess.on("exit", (code) => {
3773
4164
  if (!resolved) {
3774
4165
  resolved = true;
3775
- reject(new Error(`CLI server exited with code ${code}`));
4166
+ const stderrOutput = this.stderrBuffer.trim();
4167
+ if (stderrOutput) {
4168
+ reject(new Error(`CLI server exited with code ${code}
4169
+ stderr: ${stderrOutput}`));
4170
+ } else {
4171
+ reject(new Error(`CLI server exited with code ${code}`));
4172
+ }
3776
4173
  } else if (this.options.autoRestart && this.state === "connected") {
3777
4174
  this.reconnect();
3778
4175
  }
@@ -3786,13 +4183,15 @@ class CopilotClient {
3786
4183
  });
3787
4184
  }
3788
4185
  async connectToServer() {
3789
- if (this.options.useStdio) {
3790
- return this.connectViaStdio();
4186
+ if (this.options.isChildProcess) {
4187
+ return this.connectToParentProcessViaStdio();
4188
+ } else if (this.options.useStdio) {
4189
+ return this.connectToChildProcessViaStdio();
3791
4190
  } else {
3792
4191
  return this.connectViaTcp();
3793
4192
  }
3794
4193
  }
3795
- async connectViaStdio() {
4194
+ async connectToChildProcessViaStdio() {
3796
4195
  if (!this.cliProcess) {
3797
4196
  throw new Error("CLI process not started");
3798
4197
  }
@@ -3801,7 +4200,15 @@ class CopilotClient {
3801
4200
  throw err;
3802
4201
  }
3803
4202
  });
3804
- this.connection = import_node.createMessageConnection(new import_node.StreamMessageReader(this.cliProcess.stdout), new import_node.StreamMessageWriter(this.cliProcess.stdin));
4203
+ this.connection = import_node2.createMessageConnection(new import_node2.StreamMessageReader(this.cliProcess.stdout), new import_node2.StreamMessageWriter(this.cliProcess.stdin));
4204
+ this.attachConnectionHandlers();
4205
+ this.connection.listen();
4206
+ }
4207
+ async connectToParentProcessViaStdio() {
4208
+ if (this.cliProcess) {
4209
+ throw new Error("CLI child process was unexpectedly started in parent process mode");
4210
+ }
4211
+ this.connection = import_node2.createMessageConnection(new import_node2.StreamMessageReader(process.stdin), new import_node2.StreamMessageWriter(process.stdout));
3805
4212
  this.attachConnectionHandlers();
3806
4213
  this.connection.listen();
3807
4214
  }
@@ -3812,7 +4219,7 @@ class CopilotClient {
3812
4219
  return new Promise((resolve, reject) => {
3813
4220
  this.socket = new Socket;
3814
4221
  this.socket.connect(this.actualPort, this.actualHost, () => {
3815
- this.connection = import_node.createMessageConnection(new import_node.StreamMessageReader(this.socket), new import_node.StreamMessageWriter(this.socket));
4222
+ this.connection = import_node2.createMessageConnection(new import_node2.StreamMessageReader(this.socket), new import_node2.StreamMessageWriter(this.socket));
3816
4223
  this.attachConnectionHandlers();
3817
4224
  this.connection.listen();
3818
4225
  resolve();
@@ -3829,8 +4236,13 @@ class CopilotClient {
3829
4236
  this.connection.onNotification("session.event", (notification) => {
3830
4237
  this.handleSessionEventNotification(notification);
3831
4238
  });
3832
- this.connection.onRequest("tool.call", async (params) => await this.handleToolCallRequest(params));
3833
- this.connection.onRequest("permission.request", async (params) => await this.handlePermissionRequest(params));
4239
+ this.connection.onNotification("session.lifecycle", (notification) => {
4240
+ this.handleSessionLifecycleNotification(notification);
4241
+ });
4242
+ this.connection.onRequest("tool.call", async (params) => await this.handleToolCallRequestV2(params));
4243
+ this.connection.onRequest("permission.request", async (params) => await this.handlePermissionRequestV2(params));
4244
+ this.connection.onRequest("userInput.request", async (params) => await this.handleUserInputRequest(params));
4245
+ this.connection.onRequest("hooks.invoke", async (params) => await this.handleHooksInvoke(params));
3834
4246
  this.connection.onClose(() => {
3835
4247
  if (this.state === "connected" && this.options.autoRestart) {
3836
4248
  this.reconnect();
@@ -3847,7 +4259,52 @@ class CopilotClient {
3847
4259
  session._dispatchEvent(notification.event);
3848
4260
  }
3849
4261
  }
3850
- async handleToolCallRequest(params) {
4262
+ handleSessionLifecycleNotification(notification) {
4263
+ if (typeof notification !== "object" || !notification || !("type" in notification) || typeof notification.type !== "string" || !("sessionId" in notification) || typeof notification.sessionId !== "string") {
4264
+ return;
4265
+ }
4266
+ const event = notification;
4267
+ const typedHandlers = this.typedLifecycleHandlers.get(event.type);
4268
+ if (typedHandlers) {
4269
+ for (const handler of typedHandlers) {
4270
+ try {
4271
+ handler(event);
4272
+ } catch {}
4273
+ }
4274
+ }
4275
+ for (const handler of this.sessionLifecycleHandlers) {
4276
+ try {
4277
+ handler(event);
4278
+ } catch {}
4279
+ }
4280
+ }
4281
+ async handleUserInputRequest(params) {
4282
+ if (!params || typeof params.sessionId !== "string" || typeof params.question !== "string") {
4283
+ throw new Error("Invalid user input request payload");
4284
+ }
4285
+ const session = this.sessions.get(params.sessionId);
4286
+ if (!session) {
4287
+ throw new Error(`Session not found: ${params.sessionId}`);
4288
+ }
4289
+ const result = await session._handleUserInputRequest({
4290
+ question: params.question,
4291
+ choices: params.choices,
4292
+ allowFreeform: params.allowFreeform
4293
+ });
4294
+ return result;
4295
+ }
4296
+ async handleHooksInvoke(params) {
4297
+ if (!params || typeof params.sessionId !== "string" || typeof params.hookType !== "string") {
4298
+ throw new Error("Invalid hooks invoke payload");
4299
+ }
4300
+ const session = this.sessions.get(params.sessionId);
4301
+ if (!session) {
4302
+ throw new Error(`Session not found: ${params.sessionId}`);
4303
+ }
4304
+ const output = await session._handleHooksInvoke(params.hookType, params.input);
4305
+ return { output };
4306
+ }
4307
+ async handleToolCallRequestV2(params) {
3851
4308
  if (!params || typeof params.sessionId !== "string" || typeof params.toolCallId !== "string" || typeof params.toolName !== "string") {
3852
4309
  throw new Error("Invalid tool call payload");
3853
4310
  }
@@ -3857,20 +4314,24 @@ class CopilotClient {
3857
4314
  }
3858
4315
  const handler = session.getToolHandler(params.toolName);
3859
4316
  if (!handler) {
3860
- return { result: this.buildUnsupportedToolResult(params.toolName) };
4317
+ return {
4318
+ result: {
4319
+ textResultForLlm: `Tool '${params.toolName}' is not supported by this client instance.`,
4320
+ resultType: "failure",
4321
+ error: `tool '${params.toolName}' not supported`,
4322
+ toolTelemetry: {}
4323
+ }
4324
+ };
3861
4325
  }
3862
- return await this.executeToolCall(handler, params);
3863
- }
3864
- async executeToolCall(handler, request) {
3865
4326
  try {
3866
4327
  const invocation = {
3867
- sessionId: request.sessionId,
3868
- toolCallId: request.toolCallId,
3869
- toolName: request.toolName,
3870
- arguments: request.arguments
4328
+ sessionId: params.sessionId,
4329
+ toolCallId: params.toolCallId,
4330
+ toolName: params.toolName,
4331
+ arguments: params.arguments
3871
4332
  };
3872
- const result = await handler(request.arguments, invocation);
3873
- return { result: this.normalizeToolResult(result) };
4333
+ const result = await handler(params.arguments, invocation);
4334
+ return { result: this.normalizeToolResultV2(result) };
3874
4335
  } catch (error) {
3875
4336
  const message = error instanceof Error ? error.message : String(error);
3876
4337
  return {
@@ -3883,7 +4344,7 @@ class CopilotClient {
3883
4344
  };
3884
4345
  }
3885
4346
  }
3886
- async handlePermissionRequest(params) {
4347
+ async handlePermissionRequestV2(params) {
3887
4348
  if (!params || typeof params.sessionId !== "string" || !params.permissionRequest) {
3888
4349
  throw new Error("Invalid permission request payload");
3889
4350
  }
@@ -3892,7 +4353,7 @@ class CopilotClient {
3892
4353
  throw new Error(`Session not found: ${params.sessionId}`);
3893
4354
  }
3894
4355
  try {
3895
- const result = await session._handlePermissionRequest(params.permissionRequest);
4356
+ const result = await session._handlePermissionRequestV2(params.permissionRequest);
3896
4357
  return { result };
3897
4358
  } catch (_error) {
3898
4359
  return {
@@ -3902,7 +4363,7 @@ class CopilotClient {
3902
4363
  };
3903
4364
  }
3904
4365
  }
3905
- normalizeToolResult(result) {
4366
+ normalizeToolResultV2(result) {
3906
4367
  if (result === undefined || result === null) {
3907
4368
  return {
3908
4369
  textResultForLlm: "Tool returned no result",
@@ -3924,14 +4385,6 @@ class CopilotClient {
3924
4385
  isToolResultObject(value) {
3925
4386
  return typeof value === "object" && value !== null && "textResultForLlm" in value && typeof value.textResultForLlm === "string" && "resultType" in value;
3926
4387
  }
3927
- buildUnsupportedToolResult(toolName) {
3928
- return {
3929
- textResultForLlm: `Tool '${toolName}' is not supported by this client instance.`,
3930
- resultType: "failure",
3931
- error: `tool '${toolName}' not supported`,
3932
- toolTelemetry: {}
3933
- };
3934
- }
3935
4388
  async reconnect() {
3936
4389
  this.state = "disconnected";
3937
4390
  try {
@@ -4966,7 +5419,7 @@ function parseArgs(args) {
4966
5419
  return result;
4967
5420
  }
4968
5421
  function resolveProjectRoot() {
4969
- const currentFile = fileURLToPath(import.meta.url);
5422
+ const currentFile = fileURLToPath2(import.meta.url);
4970
5423
  let dir = path6.dirname(currentFile);
4971
5424
  while (true) {
4972
5425
  const candidate = path6.join(dir, "package.json");
@@ -5151,7 +5604,7 @@ var isDirectRun = (() => {
5151
5604
  if (typeof metaMain === "boolean") {
5152
5605
  return metaMain;
5153
5606
  }
5154
- const entryPath = fileURLToPath(import.meta.url);
5607
+ const entryPath = fileURLToPath2(import.meta.url);
5155
5608
  return Boolean(process.argv[1]) && path6.resolve(process.argv[1]) === path6.resolve(entryPath);
5156
5609
  })();
5157
5610
  if (isDirectRun) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willh/subtitle-correction-agent",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "影片字幕校正代理人 - 使用 GitHub Copilot SDK",
5
5
  "type": "module",
6
6
  "files": [