@agentconnect/host 0.2.0 → 0.2.1

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/host.js CHANGED
@@ -132,14 +132,21 @@ function createHostRuntime(options) {
132
132
  return;
133
133
  recordCapability(`model.${providerId}`);
134
134
  }
135
+ function recordProviderCapability(providerId) {
136
+ recordCapability(`model.${providerId}`);
137
+ }
135
138
  async function getCachedStatus(provider, options = {}) {
139
+ if (options.force) {
140
+ statusCache.delete(provider.id);
141
+ statusInFlight.delete(provider.id);
142
+ }
136
143
  const cached = statusCache.get(provider.id);
137
144
  const now = Date.now();
138
- if (cached && now - cached.at < statusCacheTtlMs) {
145
+ if (!options.force && cached && now - cached.at < statusCacheTtlMs) {
139
146
  return cached.status;
140
147
  }
141
148
  const existing = statusInFlight.get(provider.id);
142
- if (existing)
149
+ if (!options.force && existing)
143
150
  return existing;
144
151
  if (options.allowFast && provider.fastStatus) {
145
152
  try {
@@ -184,12 +191,50 @@ function createHostRuntime(options) {
184
191
  statusInFlight.set(provider.id, promise);
185
192
  return promise;
186
193
  }
194
+ async function isModelForProvider(model, providerId) {
195
+ try {
196
+ const models = await listModels();
197
+ if (!models.length)
198
+ return true;
199
+ const match = models.find((entry) => entry.id === model);
200
+ if (match)
201
+ return match.provider === providerId;
202
+ }
203
+ catch {
204
+ return true;
205
+ }
206
+ return resolveProviderForModel(model) === providerId;
207
+ }
208
+ async function pickDefaultModel(providerId) {
209
+ try {
210
+ const recent = await listRecentModels(providerId);
211
+ if (recent.length > 0)
212
+ return recent[0].id;
213
+ }
214
+ catch {
215
+ // ignore recent model lookup failures
216
+ }
217
+ try {
218
+ const models = await listModels();
219
+ const match = models.find((entry) => entry.provider === providerId);
220
+ if (match)
221
+ return match.id;
222
+ }
223
+ catch {
224
+ // ignore model lookup failures
225
+ }
226
+ if (providerId === 'claude')
227
+ return 'default';
228
+ if (providerId === 'local')
229
+ return 'local';
230
+ return null;
231
+ }
187
232
  function invalidateStatus(providerId) {
188
233
  if (!providerId)
189
234
  return;
190
235
  statusCache.delete(providerId);
191
236
  }
192
- function emitSessionEvent(responder, sessionId, type, data) {
237
+ function emitSessionEvent(emit, sessionId, type, data) {
193
238
  if (process.env.AGENTCONNECT_DEBUG?.trim()) {
194
239
  try {
195
240
  console.log(`[AgentConnect][Session ${sessionId}] ${type} ${JSON.stringify(data)}`);
@@ -198,7 +243,7 @@ function createHostRuntime(options) {
198
243
  console.log(`[AgentConnect][Session ${sessionId}] ${type}`);
199
244
  }
200
245
  }
201
- responder.emit({
246
+ emit({
202
247
  jsonrpc: '2.0',
203
248
  method: 'acp.session.event',
204
249
  params: { sessionId, type, data },
@@ -252,7 +297,10 @@ function createHostRuntime(options) {
252
297
  responder.error(id, 'AC_ERR_UNSUPPORTED', 'Unknown provider');
253
298
  return;
254
299
  }
255
- const status = await getCachedStatus(provider, { allowFast: true });
300
+ const statusOptions = params.options ?? {};
301
+ const allowFast = statusOptions.fast !== false;
302
+ const force = Boolean(statusOptions.force);
303
+ const status = await getCachedStatus(provider, { allowFast, force });
256
304
  responder.reply(id, {
257
305
  provider: {
258
306
  id: provider.id,
@@ -395,13 +443,39 @@ function createHostRuntime(options) {
395
443
  }
396
444
  if (method === 'acp.sessions.create') {
397
445
  const sessionId = `sess_${Math.random().toString(36).slice(2, 10)}`;
398
- const model = params.model || 'claude-opus';
446
+ const rawProvider = params.provider;
447
+ if (rawProvider && !providers[rawProvider]) {
448
+ responder.error(id, 'AC_ERR_UNSUPPORTED', 'Unknown provider');
449
+ return;
450
+ }
451
+ const rawModel = typeof params.model === 'string' ? params.model.trim() : '';
452
+ const modelInput = rawModel ? rawModel : undefined;
453
+ if (!rawProvider && !modelInput) {
454
+ responder.error(id, 'AC_ERR_INVALID_ARGS', 'Model or provider is required');
455
+ return;
456
+ }
457
+ const providerId = rawProvider ?? resolveProviderForModel(modelInput);
458
+ let model = modelInput ?? null;
459
+ if (rawProvider && modelInput) {
460
+ const matches = await isModelForProvider(modelInput, rawProvider);
461
+ if (!matches) {
462
+ responder.error(id, 'AC_ERR_INVALID_ARGS', 'Model does not belong to provider');
463
+ return;
464
+ }
465
+ }
466
+ if (rawProvider && !modelInput) {
467
+ model = await pickDefaultModel(rawProvider);
468
+ }
399
469
  const reasoningEffort = params.reasoningEffort || null;
400
470
  const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : undefined;
401
471
  const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : undefined;
402
472
  const providerDetailLevel = params.providerDetailLevel || undefined;
403
- const providerId = resolveProviderForModel(model);
404
- recordModelCapability(model);
473
+ if (model) {
474
+ recordModelCapability(model);
475
+ }
476
+ else {
477
+ recordProviderCapability(providerId);
478
+ }
405
479
  sessions.set(sessionId, {
406
480
  id: sessionId,
407
481
  providerId,
@@ -421,43 +495,39 @@ function createHostRuntime(options) {
421
495
  const sessionId = params.sessionId;
422
496
  const existing = sessions.get(sessionId);
423
497
  if (!existing) {
424
- const model = params.model || 'claude-opus';
425
- const reasoningEffort = params.reasoningEffort || null;
426
- const cwd = params.cwd ? resolveAppPathInternal(params.cwd) : undefined;
427
- const repoRoot = params.repoRoot ? resolveAppPathInternal(params.repoRoot) : undefined;
428
- const providerDetailLevel = params.providerDetailLevel || undefined;
429
- recordModelCapability(model);
430
- sessions.set(sessionId, {
431
- id: sessionId,
432
- providerId: resolveProviderForModel(model),
433
- model,
434
- providerSessionId: params.providerSessionId || null,
435
- reasoningEffort,
436
- cwd,
437
- repoRoot,
438
- providerDetailLevel: providerDetailLevel === 'raw' || providerDetailLevel === 'minimal'
439
- ? providerDetailLevel
440
- : undefined,
441
- });
498
+ responder.error(id, 'AC_ERR_INVALID_ARGS', 'Unknown session');
499
+ return;
442
500
  }
443
- else {
444
- if (params.providerSessionId) {
445
- existing.providerSessionId = String(params.providerSessionId);
446
- }
447
- if (params.cwd) {
448
- existing.cwd = resolveAppPathInternal(params.cwd);
449
- }
450
- if (params.repoRoot) {
451
- existing.repoRoot = resolveAppPathInternal(params.repoRoot);
501
+ if (params.providerSessionId) {
502
+ existing.providerSessionId = String(params.providerSessionId);
503
+ }
504
+ if (params.cwd) {
505
+ existing.cwd = resolveAppPathInternal(params.cwd);
506
+ }
507
+ if (params.repoRoot) {
508
+ existing.repoRoot = resolveAppPathInternal(params.repoRoot);
509
+ }
510
+ if (params.providerDetailLevel) {
511
+ const level = String(params.providerDetailLevel);
512
+ if (level === 'raw' || level === 'minimal') {
513
+ existing.providerDetailLevel = level;
452
514
  }
453
- if (params.providerDetailLevel) {
454
- const level = String(params.providerDetailLevel);
455
- if (level === 'raw' || level === 'minimal') {
456
- existing.providerDetailLevel = level;
457
- }
515
+ }
516
+ if (!existing.model && typeof params.model === 'string' && params.model.trim()) {
517
+ const candidate = params.model.trim();
518
+ const matches = await isModelForProvider(candidate, existing.providerId);
519
+ if (!matches) {
520
+ responder.error(id, 'AC_ERR_INVALID_ARGS', 'Model does not belong to provider');
521
+ return;
458
522
  }
523
+ existing.model = candidate;
524
+ }
525
+ if (existing.model) {
459
526
  recordModelCapability(existing.model);
460
527
  }
528
+ else {
529
+ recordProviderCapability(existing.providerId);
530
+ }
461
531
  responder.reply(id, { sessionId });
462
532
  return;
463
533
  }
@@ -469,7 +539,18 @@ function createHostRuntime(options) {
469
539
  responder.error(id, 'AC_ERR_INVALID_ARGS', 'Unknown session');
470
540
  return;
471
541
  }
472
- recordModelCapability(session.model);
542
+ let model = session.model;
543
+ if (!model) {
544
+ model = await pickDefaultModel(session.providerId);
545
+ if (model)
546
+ session.model = model;
547
+ }
548
+ if (model) {
549
+ recordModelCapability(model);
550
+ }
551
+ else {
552
+ recordProviderCapability(session.providerId);
553
+ }
473
554
  const provider = providers[session.providerId];
474
555
  if (!provider) {
475
556
  responder.error(id, 'AC_ERR_UNSUPPORTED', 'Unknown provider');
@@ -495,52 +576,66 @@ function createHostRuntime(options) {
495
576
  const providerDetailLevel = params.providerDetailLevel === 'raw' || params.providerDetailLevel === 'minimal'
496
577
  ? params.providerDetailLevel
497
578
  : session.providerDetailLevel || 'minimal';
498
- activeRuns.set(sessionId, controller);
579
+ const runToken = `run_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
580
+ activeRuns.set(sessionId, { controller, emit: responder.emit, token: runToken });
499
581
  let sawError = false;
500
582
  provider
501
583
  .runPrompt({
502
584
  prompt: message,
503
585
  resumeSessionId: session.providerSessionId,
504
- model: session.model,
586
+ model: model ?? undefined,
505
587
  reasoningEffort: session.reasoningEffort,
506
588
  repoRoot,
507
589
  cwd,
508
590
  providerDetailLevel,
509
591
  signal: controller.signal,
510
592
  onEvent: (event) => {
593
+ const current = activeRuns.get(sessionId);
594
+ if (!current || current.token !== runToken)
595
+ return;
511
596
  if (event.type === 'error') {
512
597
  sawError = true;
513
598
  }
514
599
  if (sawError && event.type === 'final') {
515
600
  return;
516
601
  }
517
- emitSessionEvent(responder, sessionId, event.type, { ...event });
602
+ emitSessionEvent(current.emit, sessionId, event.type, { ...event });
518
603
  },
519
604
  })
520
605
  .then((result) => {
606
+ const current = activeRuns.get(sessionId);
607
+ if (!current || current.token !== runToken)
608
+ return;
521
609
  if (result?.sessionId) {
522
610
  session.providerSessionId = result.sessionId;
523
611
  }
524
612
  })
525
613
  .catch((err) => {
614
+ const current = activeRuns.get(sessionId);
615
+ if (!current || current.token !== runToken)
616
+ return;
526
617
  if (!sawError) {
527
- emitSessionEvent(responder, sessionId, 'error', {
618
+ emitSessionEvent(current.emit, sessionId, 'error', {
528
619
  message: err?.message || 'Provider error',
529
620
  });
530
621
  }
531
622
  })
532
623
  .finally(() => {
533
- activeRuns.delete(sessionId);
624
+ const current = activeRuns.get(sessionId);
625
+ if (current && current.token === runToken) {
626
+ activeRuns.delete(sessionId);
627
+ }
534
628
  });
535
629
  responder.reply(id, { accepted: true });
536
630
  return;
537
631
  }
538
632
  if (method === 'acp.sessions.cancel') {
539
633
  const sessionId = params.sessionId;
540
- const controller = activeRuns.get(sessionId);
541
- if (controller) {
542
- controller.abort();
634
+ const run = activeRuns.get(sessionId);
635
+ if (run) {
543
636
  activeRuns.delete(sessionId);
637
+ run.controller.abort();
638
+ emitSessionEvent(run.emit, sessionId, 'final', { cancelled: true });
544
639
  }
545
640
  responder.reply(id, { cancelled: true });
546
641
  return;
package/dist/types.d.ts CHANGED
@@ -144,6 +144,7 @@ export interface SessionEvent {
144
144
  input?: unknown;
145
145
  output?: unknown;
146
146
  timestampMs?: number;
147
+ cancelled?: boolean;
147
148
  }
148
149
  export interface RunPromptOptions {
149
150
  prompt: string;
@@ -182,7 +183,7 @@ export interface Provider {
182
183
  export interface SessionState {
183
184
  id: string;
184
185
  providerId: ProviderId;
185
- model: string;
186
+ model: string | null;
186
187
  providerSessionId: string | null;
187
188
  reasoningEffort: string | null;
188
189
  cwd?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentconnect/host",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/rayzhudev/agent-connect",