@inixiative/agent-session 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inixiative/agent-session",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Drive coding-agent CLIs as persistent, streaming, event-captured sessions over interchangeable transports, with primed decision sessions, subscription pools, routing, limit polling and continuity.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -35,11 +35,11 @@
35
35
  ],
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "@inixiative/json-rules": "^3.4.1"
38
+ "@inixiative/json-rules": "^3.5.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@biomejs/biome": "2.5.2",
42
- "@inixiative/config": "^0.5.62",
42
+ "@inixiative/config": "^0.5.63",
43
43
  "@types/bun": "1.4.2",
44
44
  "lefthook": "2.1.9",
45
45
  "typescript": "6.0.3"
package/src/index.ts CHANGED
@@ -86,6 +86,7 @@ export {
86
86
  } from './primed';
87
87
  export {
88
88
  assessCandidate,
89
+ type BillingMode,
89
90
  type CandidateRequest,
90
91
  DEFAULT_STRATEGY,
91
92
  type ExclusionReason,
package/src/routing.ts CHANGED
@@ -20,6 +20,7 @@
20
20
  // ---------------------------------------------------------------------------
21
21
 
22
22
  import type { OrderBy, SortDir } from '@inixiative/json-rules';
23
+ import { orderRecords } from '@inixiative/json-rules';
23
24
 
24
25
  export type { OrderBy, SortDir };
25
26
 
@@ -127,6 +128,16 @@ export interface SubscriptionCandidate {
127
128
  */
128
129
  export type UnknownUtilizationPolicy = 'exclude' | 'rank-last';
129
130
 
131
+ /**
132
+ * How an instance is paid for. Routing serves only `subscription` unless the
133
+ * caller widens it: spending real money is the caller's decision, never a
134
+ * routing default. Kingdom widens to `api-key`, where a run's spend is bounded
135
+ * by its allocation policies.
136
+ */
137
+ export type BillingMode = 'subscription' | 'api-key';
138
+
139
+ const DEFAULT_BILLING: readonly BillingMode[] = ['subscription'];
140
+
130
141
  export interface RoutingRequest {
131
142
  repository: string;
132
143
  organizationId?: string;
@@ -138,6 +149,7 @@ export interface RoutingRequest {
138
149
  ordering?: OrderingSpec;
139
150
  pinned?: boolean;
140
151
  unknownUtilization?: UnknownUtilizationPolicy;
152
+ allowedBilling?: readonly BillingMode[];
141
153
  }
142
154
 
143
155
  /** Repository resolution already done by the caller (or not needed). */
@@ -153,6 +165,8 @@ export interface CandidateRequest {
153
165
  /** Only the preferred instance may serve; others are excluded as `not-pinned`. */
154
166
  pinned?: boolean;
155
167
  unknownUtilization?: UnknownUtilizationPolicy;
168
+ /** Billing modes this request may use. Default: subscription only. */
169
+ allowedBilling?: readonly BillingMode[];
156
170
  }
157
171
 
158
172
  export interface RankedCandidate {
@@ -167,7 +181,7 @@ export interface RankedCandidate {
167
181
 
168
182
  export type ExclusionReason =
169
183
  | 'disabled'
170
- | 'not-subscription'
184
+ | 'billing'
171
185
  | 'organization'
172
186
  | 'runtime'
173
187
  | 'model'
@@ -229,7 +243,8 @@ export function assessCandidate(
229
243
  )
230
244
  return { excluded: 'invalid' };
231
245
  if (!account.enabled) return { excluded: 'disabled' };
232
- if (account.authentication !== 'subscription') return { excluded: 'not-subscription' };
246
+ if (!(request.allowedBilling ?? DEFAULT_BILLING).includes(account.authentication))
247
+ return { excluded: 'billing' };
233
248
  if (
234
249
  request.organizationId !== undefined &&
235
250
  !account.organizationIds.includes(request.organizationId)
@@ -275,20 +290,16 @@ export function assessCandidate(
275
290
  return { utilizationPercent: Math.max(...fractions) };
276
291
  }
277
292
 
278
- const FIELD_VALUE: Record<RoutingField, (candidate: RankedCandidate) => number | null> = {
279
- priority: (c) => c.priority,
280
- utilization: (c) => c.utilizationPercent,
281
- quartile: (c) => c.quartile,
282
- headroom: (c) => c.headroom,
283
- preferred: (c) => Number(c.preferred),
284
- };
285
-
286
- /** Nulls last in either direction: an unknown reading never outranks a known one. */
287
- const compare = (a: number | null, b: number | null, dir: SortDir): number => {
288
- if (a === null) return b === null ? 0 : 1;
289
- if (b === null) return -1;
290
- return dir === 'desc' ? b - a : a - b;
291
- };
293
+ /** Term fields as own properties, so json-rules orders them by name. */
294
+ const sortKey = (candidate: RankedCandidate, index: number) => ({
295
+ index,
296
+ priority: candidate.priority,
297
+ utilization: candidate.utilizationPercent,
298
+ quartile: candidate.quartile,
299
+ headroom: candidate.headroom,
300
+ preferred: Number(candidate.preferred),
301
+ id: candidate.accountId,
302
+ });
292
303
 
293
304
  /** Rank eligible candidates deterministically (ties broken by id). */
294
305
  export function rankCandidates(
@@ -329,14 +340,8 @@ export function rankCandidates(
329
340
  },
330
341
  ];
331
342
  });
332
- ranked.sort((a, b) => {
333
- for (const { field, dir } of ordering) {
334
- const order = compare(FIELD_VALUE[field](a), FIELD_VALUE[field](b), dir);
335
- if (order !== 0) return order;
336
- }
337
- return a.accountId.localeCompare(b.accountId);
338
- });
339
- return { candidates: ranked, excluded, ordering };
343
+ const ordered = orderRecords(ranked.map(sortKey), [...ordering, { field: 'id', dir: 'asc' }]);
344
+ return { candidates: ordered.map((key) => ranked[key.index]!), excluded, ordering };
340
345
  }
341
346
 
342
347
  /** The draft's repository-scoped entry point: resolve ownership, then rank. */
@@ -373,6 +378,7 @@ export function rankSubscriptionAccounts(
373
378
  ...(request.unknownUtilization !== undefined
374
379
  ? { unknownUtilization: request.unknownUtilization }
375
380
  : {}),
381
+ ...(request.allowedBilling !== undefined ? { allowedBilling: request.allowedBilling } : {}),
376
382
  },
377
383
  accounts,
378
384
  );