@north-light/cli 0.3.501 → 0.3.503

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 (42) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/grants.d.ts +2 -2
  4. package/dist/api/dto/run-events.d.ts +3 -1
  5. package/dist/builtin-memory/crouter-sdk/release-notes.md +10 -0
  6. package/dist/clients/attach/viewer.js +433 -433
  7. package/dist/core/canvas/migrations.js +1 -1
  8. package/dist/core/fault-classifier.d.ts +2 -0
  9. package/dist/core/fault-classifier.js +1 -1
  10. package/dist/core/grants/mirror.d.ts +16 -2
  11. package/dist/core/grants/mirror.js +3 -3
  12. package/dist/core/grants/sync.js +1 -1
  13. package/dist/core/llm/app-rungs.d.ts +16 -0
  14. package/dist/core/llm/app-rungs.js +1 -0
  15. package/dist/core/llm/run.d.ts +10 -1
  16. package/dist/core/llm/run.js +2 -2
  17. package/dist/core/model-routes.d.ts +2 -0
  18. package/dist/core/model-routes.js +1 -1
  19. package/dist/core/peer/ingest.d.ts +5 -1
  20. package/dist/core/peer/ingest.js +1 -1
  21. package/dist/core/peer/outbox-store.d.ts +5 -1
  22. package/dist/core/peer/outbox-store.js +5 -4
  23. package/dist/core/providers/token-cache.d.ts +1 -1
  24. package/dist/core/providers/token-cache.js +1 -1
  25. package/dist/core/runs/operations.js +5 -5
  26. package/dist/core/runtime/broker/model-adapter.js +2 -2
  27. package/dist/core/runtime/launch.d.ts +13 -1
  28. package/dist/core/runtime/launch.js +1 -1
  29. package/dist/core/runtime/spawn.d.ts +4 -1
  30. package/dist/core/runtime/spawn.js +2 -2
  31. package/dist/daemon/api/app-listener.js +13 -13
  32. package/dist/daemon/api/handlers/llm.js +3 -3
  33. package/dist/daemon/api/handlers/nodes.js +1 -1
  34. package/dist/daemon/api/handlers/objects.js +3 -3
  35. package/dist/daemon/api/handlers/runs.js +1 -1
  36. package/dist/daemon/cron/armed-context.js +1 -1
  37. package/dist/daemon/peer/outbox.js +1 -1
  38. package/dist/daemon/runs/append-section.js +2 -2
  39. package/docs/sdk/release-notes.md +10 -0
  40. package/package.json +2 -2
  41. package/packages/crouter-identity/package.json +1 -1
  42. package/runtime.lock.json +12 -12
@@ -1046,4 +1046,4 @@ CREATE INDEX idx_structured_requests_effects ON structured_requests(effects_done
1046
1046
  CREATE UNIQUE INDEX objects_named ON objects(owner, name)
1047
1047
  WHERE deleted_at IS NULL AND home_machine IS NULL AND (type = 'document' OR instr(type, '.') > 0);
1048
1048
  CREATE UNIQUE INDEX objects_handle ON objects(type, name) WHERE home_machine IS NULL AND type IN ('person','app','repo','profile');
1049
- `)];function B(e,t,n){const o=e.prepare("PRAGMA foreign_keys").get().foreign_keys;e.exec("PRAGMA foreign_keys = OFF;");try{e.exec("BEGIN IMMEDIATE");try{W(e)===t&&(n(),e.exec(`PRAGMA user_version = ${t+1}`)),e.exec("COMMIT")}catch(T){try{e.exec("ROLLBACK")}catch{}throw T}}finally{e.exec(`PRAGMA foreign_keys = ${o};`)}}E(B,"selfTransactionalMigration");const bt=new Set([3,61,63,66]);function W(e){return e.prepare("PRAGMA user_version").get().user_version}E(W,"currentUserVersion");function se(e,t){F[t](e),e.exec(`PRAGMA user_version = ${t+1}`)}E(se,"applyMigration");function on(e){const t=W(e);if(t>F.length)throw new Error(`unsupported canvas schema version ${t} (latest supported ${F.length})`);if(t===F.length)return;const n=e.prepare("PRAGMA busy_timeout").get().timeout;e.exec("PRAGMA busy_timeout = 60000;");try{Bt(e)}finally{e.exec(`PRAGMA busy_timeout = ${n};`)}}E(on,"migrate");function Bt(e){for(;;){const t=W(e);if(t>=F.length)return;if(bt.has(t)){if(e.isTransaction)throw new Error(`canvas migration v${t+1} requires autocommit`);F[t](e);continue}if(e.isTransaction){se(e,t);continue}e.exec("BEGIN IMMEDIATE");try{W(e)===t&&se(e,t),e.exec("COMMIT")}catch(n){try{e.exec("ROLLBACK")}catch{}throw n}}}E(Bt,"runPendingMigrations");export{F as MIGRATIONS,on as migrate};
1049
+ `),e=>e.exec("ALTER TABLE grants ADD COLUMN inference_endpoint_json TEXT")];function B(e,t,n){const o=e.prepare("PRAGMA foreign_keys").get().foreign_keys;e.exec("PRAGMA foreign_keys = OFF;");try{e.exec("BEGIN IMMEDIATE");try{W(e)===t&&(n(),e.exec(`PRAGMA user_version = ${t+1}`)),e.exec("COMMIT")}catch(T){try{e.exec("ROLLBACK")}catch{}throw T}}finally{e.exec(`PRAGMA foreign_keys = ${o};`)}}E(B,"selfTransactionalMigration");const bt=new Set([3,61,63,66]);function W(e){return e.prepare("PRAGMA user_version").get().user_version}E(W,"currentUserVersion");function se(e,t){F[t](e),e.exec(`PRAGMA user_version = ${t+1}`)}E(se,"applyMigration");function on(e){const t=W(e);if(t>F.length)throw new Error(`unsupported canvas schema version ${t} (latest supported ${F.length})`);if(t===F.length)return;const n=e.prepare("PRAGMA busy_timeout").get().timeout;e.exec("PRAGMA busy_timeout = 60000;");try{Bt(e)}finally{e.exec(`PRAGMA busy_timeout = ${n};`)}}E(on,"migrate");function Bt(e){for(;;){const t=W(e);if(t>=F.length)return;if(bt.has(t)){if(e.isTransaction)throw new Error(`canvas migration v${t+1} requires autocommit`);F[t](e);continue}if(e.isTransaction){se(e,t);continue}e.exec("BEGIN IMMEDIATE");try{W(e)===t&&se(e,t),e.exec("COMMIT")}catch(n){try{e.exec("ROLLBACK")}catch{}throw n}}}E(Bt,"runPendingMigrations");export{F as MIGRATIONS,on as migrate};
@@ -31,4 +31,6 @@ export interface FaultClassification {
31
31
  disposition: FaultRetryDisposition;
32
32
  }
33
33
  export declare function errorClassFromFault(fault: Pick<Fault, 'kind' | 'retry'>): ErrorClass;
34
+ /** The daemon `llm` refusal code for a transient failure of the model behind it (the endpoint, its vendor, or the directory mint). */
35
+ export declare const UPSTREAM_MODEL_UNAVAILABLE = "upstream_model_unavailable";
34
36
  export declare function classify(link: FaultLink, raw: unknown): FaultClassification;
@@ -1 +1 @@
1
- var l=Object.defineProperty;var i=(e,o)=>l(e,"name",{value:o,configurable:!0});import{hasManagedProviderCoolingDiagnostic as u}from"./runtime/managed-provider-cooling.js";import{recognizeProviderError as f}from"./provider-error-recognition.js";import{extractProviderFailureDetails as p}from"./provider-failure.js";import{isRecord as a}from"../shared/predicates.js";const m={"rate-limit":"rate_limit",overloaded:"overloaded",connection:"connection",auth:"auth",protocol:"protocol","context-overflow":"context_overflow",wedged:"wedged","model-not-found":"model_not_found",other:"unknown"};function b(e){return{class:m[e.kind],retry_disposition:e.retry.disposition}}i(b,"errorClassFromFault");function v(e){switch(e.kind){case"rate-limit":return"rate-limit";case"auth":return"auth";case"protocol":return"protocol";case"context-overflow":return"context-overflow";case"overloaded":case"overloaded-only":case"transient-http":return"overloaded";case"connection":case"connection-only":return"connection";case"socket":case"service-unavailable":case"other":return"other"}}i(v,"faultKindFromProviderSignal");function g(e,o){return o==="auth"||o==="protocol"||o==="context-overflow"?"fatal":e==="pi\u2192provider"?o==="connection"||o==="rate-limit"||o==="overloaded"?"auto":"fatal":"manual"}i(g,"classifyDisposition");function y(e){if(typeof e=="string")return e;if(!a(e))return"";const o=[e.message,e.errorMessage,e.reason,e.body,e.statusText];for(const t of o)if(typeof t=="string"&&t!=="")return t;const r=e.error;if(a(r)){const t=r.message;if(typeof t=="string"&&t!=="")return t;const s=r.reason;if(typeof s=="string"&&s!=="")return s}return""}i(y,"rawText");function M(e,o){if(e==="pi\u2192provider"&&u(o))return{kind:"rate-limit",disposition:"auto"};const r=a(o)?o:{},t=p(o),s=t?.causes??[],d=f({code:r.code??t?.code??s.find(n=>n.code!==void 0)?.code,errno:r.errno??t?.errno??s.find(n=>n.errno!==void 0)?.errno,status:r.status??t?.status,wsCloseCode:r.code,wsCloseReason:r.reason,message:[y(o),t?.body,...s.flatMap(n=>[n.message,n.body])].filter(n=>typeof n=="string"&&n!=="").join(" ")}),c=v(d);return{kind:c,disposition:g(e,c)}}i(M,"classify");export{M as classify,b as errorClassFromFault};
1
+ var l=Object.defineProperty;var s=(e,o)=>l(e,"name",{value:o,configurable:!0});import{hasManagedProviderCoolingDiagnostic as f}from"./runtime/managed-provider-cooling.js";import{recognizeProviderError as p}from"./provider-error-recognition.js";import{extractProviderFailureDetails as m}from"./provider-failure.js";import{isRecord as c}from"../shared/predicates.js";const v={"rate-limit":"rate_limit",overloaded:"overloaded",connection:"connection",auth:"auth",protocol:"protocol","context-overflow":"context_overflow",wedged:"wedged","model-not-found":"model_not_found",other:"unknown"};function F(e){return{class:v[e.kind],retry_disposition:e.retry.disposition}}s(F,"errorClassFromFault");function g(e){switch(e.kind){case"rate-limit":return"rate-limit";case"auth":return"auth";case"protocol":return"protocol";case"context-overflow":return"context-overflow";case"overloaded":case"overloaded-only":case"transient-http":return"overloaded";case"connection":case"connection-only":return"connection";case"socket":case"service-unavailable":case"other":return"other"}}s(g,"faultKindFromProviderSignal");function d(e,o){return o==="auth"||o==="protocol"||o==="context-overflow"?"fatal":e==="pi\u2192provider"?o==="connection"||o==="rate-limit"||o==="overloaded"?"auto":"fatal":"manual"}s(d,"classifyDisposition");function y(e){if(typeof e=="string")return e;if(!c(e))return"";const o=[e.message,e.errorMessage,e.reason,e.body,e.statusText];for(const t of o)if(typeof t=="string"&&t!=="")return t;const r=e.error;if(c(r)){const t=r.message;if(typeof t=="string"&&t!=="")return t;const i=r.reason;if(typeof i=="string"&&i!=="")return i}return""}s(y,"rawText");const h="upstream_model_unavailable";function P(e,o){if(e==="pi\u2192provider"&&f(o))return{kind:"rate-limit",disposition:"auto"};const r=c(o)?o:{},t=m(o),i=t?.causes??[];if(t?.code===h)return{kind:"connection",disposition:d(e,"connection")};const u=p({code:r.code??t?.code??i.find(n=>n.code!==void 0)?.code,errno:r.errno??t?.errno??i.find(n=>n.errno!==void 0)?.errno,status:r.status??t?.status,wsCloseCode:r.code,wsCloseReason:r.reason,message:[y(o),t?.body,...i.flatMap(n=>[n.message,n.body])].filter(n=>typeof n=="string"&&n!=="").join(" ")}),a=g(u);return{kind:a,disposition:d(e,a)}}s(P,"classify");export{h as UPSTREAM_MODEL_UNAVAILABLE,P as classify,F as errorClassFromFault};
@@ -1,11 +1,17 @@
1
1
  import type { DatabaseSync } from 'node:sqlite';
2
2
  import { type Scope } from '@north-light/identity';
3
3
  import type { ProviderDependency } from '../../api/dto/grants.js';
4
+ import type { ModelStrength } from '../../types.js';
4
5
  export interface GrantScope {
5
6
  scope: string;
6
7
  mode: 'always' | 'ask';
7
8
  }
8
9
  export type GrantDependencies = Record<string, ProviderDependency>;
10
+ /** An app-paid grant's inference endpoint: where its model calls go and the catalog model each strength (rung) names. */
11
+ export interface InferenceEndpoint {
12
+ base_url: string;
13
+ rungs: Partial<Record<ModelStrength, string>>;
14
+ }
9
15
  export interface GrantRecord {
10
16
  id: string;
11
17
  runtime: string;
@@ -24,6 +30,8 @@ export interface GrantRecord {
24
30
  dependencies?: GrantDependencies;
25
31
  /** The grantee's registered run-notice endpoint (https), or null: runs of this grant send no notice. */
26
32
  run_notice_url?: string | null;
33
+ /** The endpoint of an app-paid grant (`payer: 'app'`); null on a user-paid grant or one the directory sent no valid endpoint for. */
34
+ inference_endpoint?: InferenceEndpoint | null;
27
35
  synced_at: string;
28
36
  }
29
37
  export interface ActiveGrant extends GrantRecord {
@@ -34,6 +42,10 @@ export interface ActiveGrant extends GrantRecord {
34
42
  * authorization): absent or non-object reads as `{}`, and a malformed entry
35
43
  * (see `dependencyOf`) is dropped. A directory older than the Context stage sends none. */
36
44
  export declare function dependenciesOf(value: unknown): GrantDependencies;
45
+ /** The `inference_endpoint` a grant sync answer carried: `{ base_url, rungs }` with a non-empty string `base_url` and at
46
+ * least one known strength naming a catalog model id. Anything else reads as null — an app-paid grant without a usable
47
+ * endpoint is refused `model_unavailable` at its first call, never served from the owner's routes. */
48
+ export declare function inferenceEndpointOf(value: unknown): InferenceEndpoint | null;
37
49
  /** A grant this machine only mirrors. An unsandboxed machine runs only
38
50
  * `app:terminal`; the directory also fans the account's hosted-runtime app
39
51
  * grants out to it, read-only, so a forwarded app caller can be authorized
@@ -50,9 +62,9 @@ export declare function grantExpired(grant: Pick<GrantRecord, 'expires_at'>): bo
50
62
  export declare function activeRecord(grant: GrantRecord): ActiveGrant | null;
51
63
  /** `grant.get`'s `version`: an opaque string that changes exactly when what an app
52
64
  * reads from its grant changes — `rev`, `resolved_scopes` (order kept), `spend_cap`,
53
- * `payer`, `dependencies`. `synced_at` and the token's claims are left out, so a
65
+ * `payer`, `inference_endpoint`, `dependencies`. `synced_at` and the token's claims are left out, so a
54
66
  * periodic sync with the same content (or a daemon restart) keeps it. */
55
- export declare function grantVersion(grant: Pick<GrantRecord, 'rev' | 'resolved_scopes' | 'spend_cap' | 'payer' | 'dependencies'>): string;
67
+ export declare function grantVersion(grant: Pick<GrantRecord, 'rev' | 'resolved_scopes' | 'spend_cap' | 'payer' | 'dependencies' | 'inference_endpoint'>): string;
56
68
  export declare function hasGrant(grantId: string): boolean;
57
69
  /** The grant state the freshness ordering reads (contract 1 "Grant sync"):
58
70
  * the mirrored grant's revision and status, whatever its status. */
@@ -66,6 +78,8 @@ export declare function grantSyncState(grantId: string): GrantSyncState | null;
66
78
  export declare function grantSyncStateFor(grantee: string, sub: string): GrantSyncState | null;
67
79
  /** The mirrored grant `grantId`, whatever its status, or null when the mirror lacks it or the row is unparsable. */
68
80
  export declare function grantRecord(grantId: string): GrantRecord | null;
81
+ /** Who a grant's model calls are charged to: `app` only for a mirrored grant the directory marked app-paid, else the person. */
82
+ export declare function payerOfGrant(grantId: string | null | undefined): 'user' | 'app';
69
83
  export declare function activeGrant(grantId: string): ActiveGrant | null;
70
84
  export declare function grantFor(grantee: string, sub: string): ActiveGrant | null;
71
85
  /** The app's own active grant on this machine. Null for a mirror-only grant: an
@@ -1,4 +1,4 @@
1
- var _=Object.defineProperty;var t=(e,r)=>_(e,"name",{value:r,configurable:!0});import{createHash as y}from"node:crypto";import{prepared as s}from"../sqlite-statements.js";import{parseScope as S}from"@north-light/identity";import{openDb as i,withCanvasWrite as v}from"../canvas/db.js";import{isUnsandboxedMachine as T}from"../runtime/unsandboxed.js";const m=["not_started","pending","ready","failed","unavailable"];function c(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(c,"entries");function O(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:a,next:u,step:n}=e;return typeof r!="string"||!m.includes(r)||a!==void 0&&typeof a!="string"||n!==void 0&&typeof n!="string"||typeof u!="string"||!u?null:{state:r,...a===void 0?{}:{message:a},next:u,...n===void 0?{}:{step:n}}}t(O,"dependencyOf");function E(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[a,u]of Object.entries(e)){const n=O(u);n&&(r[a]=n)}return r}t(E,"dependenciesOf");function l(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:c(JSON.parse(e.scopes_json)),resolved_scopes:c(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:E(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,synced_at:e.synced_at}}t(l,"record");function p(e){if(!e||e.status!=="active")return null;try{return N(l(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(p,"active");function d(e){return e.grantee!=="app:terminal"&&T()}t(d,"mirrorOnly");function b(){return R().filter(e=>!d(e))}t(b,"localGrants");function R(){return s(i(),"SELECT * FROM grants").all().flatMap(e=>{try{return[l(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(R,"allGrants");function x(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t(x,"grantExpired");function N(e){if(e.status!=="active"||x(e))return null;try{const r=e.resolved_scopes.map(({scope:a})=>S(a));return{...e,resolved:e.resolved_scopes.flatMap(({mode:a},u)=>a==="always"?[r[u]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(N,"activeRecord");function f(e){return Array.isArray(e)?`[${e.map(f).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${f(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(f,"canonicalJson");function k(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{}};return y("sha256").update(f(r)).digest("base64url").slice(0,22)}t(k,"grantVersion");function D(e){return!!s(i(),"SELECT 1 FROM grants WHERE id = ?").get(e)}t(D,"hasGrant");function H(e){return s(i(),"SELECT rev, status FROM grants WHERE id = ?").get(e)??null}t(H,"grantSyncState");function W(e,r){return s(i(),"SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,r)??null}t(W,"grantSyncStateFor");function j(e){const r=s(i(),"SELECT * FROM grants WHERE id = ?").get(e);if(!r)return null;try{return l(r)}catch(a){if(a instanceof TypeError||a instanceof SyntaxError)return null;throw a}}t(j,"grantRecord");function G(e){return p(s(i(),"SELECT * FROM grants WHERE id = ?").get(e))}t(G,"activeGrant");function I(e,r){return p(s(i(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,r))}t(I,"grantFor");function J(e){if(d({grantee:e}))return null;const r=s(i(),"SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return p(r[0])}t(J,"activeGrantForApp");function U(e){const r=s(i(),"SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return r&&r.url&&!d(r)?{url:r.url,sub:r.sub,grantee:r.grantee}:null}t(U,"runNoticeTarget");function w(){const e=s(i(),"SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return p(e[0])}t(w,"terminalGrant");function $(){return s(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value??null}t($,"grantsSource");function V(e){return s(i(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value===e}t(V,"hasSyncedGrants");function P(e){return s(e,"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value!=="local"?0:(s(e,"DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number(s(e,"DELETE FROM grants").run().changes))}t(P,"dropLocalGrantMirror");function X(e,r,a){const u=new Set;for(const n of e){if(u.has(n.id)||!n.id||!n.runtime||!n.sub||!n.grantee||!Number.isInteger(n.rev)||!["active","revoked","removed"].includes(n.status))throw new TypeError("invalid grant sync response");u.add(n.id),c(n.scopes),c(n.resolved_scopes)}v(n=>{n.exec("DELETE FROM grants");const g=s(n,`INSERT INTO grants
1
+ var y=Object.defineProperty;var t=(e,r)=>y(e,"name",{value:r,configurable:!0});import{createHash as g}from"node:crypto";import{prepared as o}from"../sqlite-statements.js";import{parseScope as S}from"@north-light/identity";import{openDb as u,withCanvasWrite as T}from"../canvas/db.js";import{isUnsandboxedMachine as m}from"../runtime/unsandboxed.js";const v=["not_started","pending","ready","failed","unavailable"],O=["ultra","strong","medium","light"];function p(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(p,"entries");function x(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:s,next:i,step:n}=e;return typeof r!="string"||!v.includes(r)||s!==void 0&&typeof s!="string"||n!==void 0&&typeof n!="string"||typeof i!="string"||!i?null:{state:r,...s===void 0?{}:{message:s},next:i,...n===void 0?{}:{step:n}}}t(x,"dependencyOf");function _(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[s,i]of Object.entries(e)){const n=x(i);n&&(r[s]=n)}return r}t(_,"dependenciesOf");function R(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:s}=e;if(typeof r!="string"||!r||!s||typeof s!="object"||Array.isArray(s))return null;const i={};for(const n of O){const c=s[n];typeof c=="string"&&c&&(i[n]=c)}return Object.keys(i).length?{base_url:r.replace(/\/+$/,""),rungs:i}:null}t(R,"inferenceEndpointOf");function f(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:p(JSON.parse(e.scopes_json)),resolved_scopes:p(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:_(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?R(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at}}t(f,"record");function l(e){if(!e||e.status!=="active")return null;try{return h(f(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(l,"active");function d(e){return e.grantee!=="app:terminal"&&m()}t(d,"mirrorOnly");function H(){return N().filter(e=>!d(e))}t(H,"localGrants");function N(){return o(u(),"SELECT * FROM grants").all().flatMap(e=>{try{return[f(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(N,"allGrants");function A(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t(A,"grantExpired");function h(e){if(e.status!=="active"||A(e))return null;try{const r=e.resolved_scopes.map(({scope:s})=>S(s));return{...e,resolved:e.resolved_scopes.flatMap(({mode:s},i)=>s==="always"?[r[i]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(h,"activeRecord");function E(e){return Array.isArray(e)?`[${e.map(E).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${E(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(E,"canonicalJson");function D(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return g("sha256").update(E(r)).digest("base64url").slice(0,22)}t(D,"grantVersion");function W(e){return!!o(u(),"SELECT 1 FROM grants WHERE id = ?").get(e)}t(W,"hasGrant");function G(e){return o(u(),"SELECT rev, status FROM grants WHERE id = ?").get(e)??null}t(G,"grantSyncState");function J(e,r){return o(u(),"SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,r)??null}t(J,"grantSyncStateFor");function b(e){const r=o(u(),"SELECT * FROM grants WHERE id = ?").get(e);if(!r)return null;try{return f(r)}catch(s){if(s instanceof TypeError||s instanceof SyntaxError)return null;throw s}}t(b,"grantRecord");function I(e){return(e?b(e)?.payer:null)??"user"}t(I,"payerOfGrant");function U(e){return l(o(u(),"SELECT * FROM grants WHERE id = ?").get(e))}t(U,"activeGrant");function $(e,r){return l(o(u(),"SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,r))}t($,"grantFor");function w(e){if(d({grantee:e}))return null;const r=o(u(),"SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return l(r[0])}t(w,"activeGrantForApp");function V(e){const r=o(u(),"SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return r&&r.url&&!d(r)?{url:r.url,sub:r.sub,grantee:r.grantee}:null}t(V,"runNoticeTarget");function P(){const e=o(u(),"SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return l(e[0])}t(P,"terminalGrant");function X(){return o(u(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value??null}t(X,"grantsSource");function q(e){return o(u(),"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value===e}t(q,"hasSyncedGrants");function z(e){return o(e,"SELECT value FROM runtime_state WHERE key = 'grants_source'").get()?.value!=="local"?0:(o(e,"DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number(o(e,"DELETE FROM grants").run().changes))}t(z,"dropLocalGrantMirror");function B(e,r,s){const i=new Set;for(const n of e){if(i.has(n.id)||!n.id||!n.runtime||!n.sub||!n.grantee||!Number.isInteger(n.rev)||!["active","revoked","removed"].includes(n.status))throw new TypeError("invalid grant sync response");i.add(n.id),p(n.scopes),p(n.resolved_scopes)}T(n=>{n.exec("DELETE FROM grants");const c=o(n,`INSERT INTO grants
2
2
  (id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
3
- status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, synced_at)
4
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const o of e)g.run(o.id,o.runtime,o.sub,o.grantee,o.rev,JSON.stringify(o.scopes),JSON.stringify(o.resolved_scopes),o.spend_cap,o.expires_at,o.status,o.revoked_at,o.approved_at,o.payer,JSON.stringify(E(o.dependencies)),o.run_notice_url??null,o.synced_at);s(n,"INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(a),s(n,"INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT)").run()})}t(X,"replaceGrants");function q(){const e=s(i(),"SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(q,"grantsGeneration");export{G as activeGrant,J as activeGrantForApp,N as activeRecord,R as allGrants,E as dependenciesOf,P as dropLocalGrantMirror,x as grantExpired,I as grantFor,j as grantRecord,H as grantSyncState,W as grantSyncStateFor,k as grantVersion,q as grantsGeneration,$ as grantsSource,D as hasGrant,V as hasSyncedGrants,b as localGrants,d as mirrorOnly,X as replaceGrants,U as runNoticeTarget,w as terminalGrant};
3
+ status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, inference_endpoint_json, synced_at)
4
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const a of e)c.run(a.id,a.runtime,a.sub,a.grantee,a.rev,JSON.stringify(a.scopes),JSON.stringify(a.resolved_scopes),a.spend_cap,a.expires_at,a.status,a.revoked_at,a.approved_at,a.payer,JSON.stringify(_(a.dependencies)),a.run_notice_url??null,a.inference_endpoint?JSON.stringify(a.inference_endpoint):null,a.synced_at);o(n,"INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(s),o(n,"INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT)").run()})}t(B,"replaceGrants");function K(){const e=o(u(),"SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(K,"grantsGeneration");export{U as activeGrant,w as activeGrantForApp,h as activeRecord,N as allGrants,_ as dependenciesOf,z as dropLocalGrantMirror,A as grantExpired,$ as grantFor,b as grantRecord,G as grantSyncState,J as grantSyncStateFor,D as grantVersion,K as grantsGeneration,X as grantsSource,W as hasGrant,q as hasSyncedGrants,R as inferenceEndpointOf,H as localGrants,d as mirrorOnly,I as payerOfGrant,B as replaceGrants,V as runNoticeTarget,P as terminalGrant};
@@ -1 +1 @@
1
- var w=Object.defineProperty;var n=(e,r)=>w(e,"name",{value:r,configurable:!0});import{createPrivateKey as v,randomUUID as S,sign as b}from"node:crypto";import{readFileSync as E}from"node:fs";import{join as j}from"node:path";import{crtrHome as x}from"../canvas/paths.js";import{isUnsandboxedMachine as g}from"../runtime/unsandboxed.js";import{emitEvent as f}from"../events/emit.js";import{allGrants as $,dependenciesOf as T,replaceGrants as A}from"./mirror.js";import{onGrantSync as N}from"./on-sync.js";const z=300;function O(e,r){const t=Math.floor(Date.now()/1e3),s=Buffer.from(JSON.stringify({alg:"ES256",typ:"JWT",kid:e.kid})).toString("base64url"),c=Buffer.from(JSON.stringify({iss:e.client_id,sub:e.client_id,aud:r,iat:t,exp:t+300,jti:S()})).toString("base64url"),o=`${s}.${c}`,i=b("sha256",Buffer.from(o),{key:v({key:e.jwk,format:"jwk"}),dsaEncoding:"ieee-p1363"});return`${o}.${i.toString("base64url")}`}n(O,"assertion");function y(){const e=JSON.parse(E(j(x(),"secrets","runtime-client.json"),"utf8"));if(!e.jwk||e.alg!=="ES256"||!e.kid||!e.client_id||!e.runtime||e.jwk.kid!==e.kid||e.jwk.kty!=="EC"||e.jwk.crv!=="P-256"||!e.jwk.d)throw new TypeError("invalid runtime client key");return e}n(y,"runtimeClient");function H(e=y()){return e.runtime.startsWith("runtime:")?e.runtime.slice(8):e.runtime}n(H,"runtimeId");function G(e){return`This machine was removed from your Northlight account. To use it again, create a join token at ${e.replace(/\/$/,"")}/account/machines, and run \`crtr sys join\` from a plain terminal.`}n(G,"machineRevokedMessage");class h extends Error{static{n(this,"MachineRevokedError")}constructor(r){super(G(r)),this.name="MachineRevokedError"}}async function R(e,r){const t=y(),s=`${e.replace(/\/$/,"")}/token`,c=new URLSearchParams({grant_type:"client_credentials",client_id:t.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:O(t,s),...r===void 0?{}:{audience:r}}),o=await fetch(s,{method:"POST",body:c,signal:AbortSignal.timeout(1e4)});if(!o.ok)throw o.status===401&&g()&&await B(o)?new h(e):new Error(`directory token request failed: ${o.status}`);const i=await o.json();if(typeof i.access_token!="string")throw new TypeError("directory token response has no access_token");return{access_token:i.access_token,expires_at:D(i.access_token,i.expires_in)}}n(R,"mintDirectoryToken");async function B(e){try{return(await e.json()).error==="invalid_client"}catch{return!1}}n(B,"isInvalidClient");function D(e,r){if(typeof r=="number"&&Number.isFinite(r)&&r>0)return Date.now()+r*1e3;try{const t=JSON.parse(Buffer.from(e.split(".")[1]??"","base64url").toString("utf8"));if(typeof t.exp=="number")return t.exp*1e3}catch{}return Date.now()+3e5}n(D,"tokenExpiry");function J(e,r){if(!e||typeof e!="object")throw new TypeError("invalid grant sync response");const t=e,s=r.runtime.startsWith("runtime:")?r.runtime:`runtime:${r.runtime}`;if(!(t.runtime===s||g()&&typeof t.runtime=="string"&&t.runtime.startsWith("runtime:")&&t.grantee!=="app:terminal")||typeof t.id!="string"||typeof t.sub!="string"||typeof t.grantee!="string"||!Number.isInteger(t.rev)||!Array.isArray(t.scopes)||!Array.isArray(t.resolved_scopes)||!["active","revoked","removed"].includes(t.status??"")||typeof t.synced_at!="string")throw new TypeError("invalid grant sync response");const i=e.revisions?.find(d=>d.rev===t.rev)?.approved_at;if(typeof i!="string")throw new TypeError("grant revision has no approval time");return{...t,spend_cap:t.spend_cap??null,expires_at:t.expires_at??null,revoked_at:t.revoked_at??null,approved_at:i,payer:"user",dependencies:T(t.dependencies),run_notice_url:typeof t.run_notice_url=="string"&&t.run_notice_url!==""?t.run_notice_url:null}}n(J,"responseGrant");let _;function K(e){return _?_(e):Promise.resolve()}n(K,"requestGrantSync");function V(e,r=t=>{}){let t;const s=e.replace(/\/$/,""),c=n(async()=>{const a=y(),u=await R(s),l=await fetch(`${s}/v1/runtime/grants`,{headers:{Authorization:`Bearer ${u.access_token}`},signal:AbortSignal.timeout(1e4)});if(!l.ok)throw new Error(`directory grant sync failed: ${l.status}`);const p=await l.json();if(!Array.isArray(p.grants)||typeof p.synced_at!="string")throw new TypeError("invalid grant sync response");const k=$();A(p.grants.map(m=>J(m,a)),p.synced_at,s),f({level:"info",event:"daemon.grants.synced",fields:{grants:p.grants.length,synced_at:p.synced_at}});try{await N(k,r)}catch(m){f({level:"error",event:"daemon.grants.on_sync_failed",error:m})}},"run");let o;const i=n(()=>{const a=c().catch(u=>{throw u instanceof h?f({level:"error",event:"daemon.grants.machine_revoked",error:u}):f({level:"error",event:"daemon.grants.sync_failed",error:u}),u}).finally(()=>{t===a&&(t=void 0)});return t=a,a},"start"),d=n((a={})=>t?a.fresh?(o??=t.catch(()=>{}).then(()=>(o=void 0,t??i())),o):t:i(),"sync");return _=d,d}n(V,"createGrantSync");export{z as GRANT_SYNC_INTERVAL_S,h as MachineRevokedError,O as assertion,V as createGrantSync,G as machineRevokedMessage,R as mintDirectoryToken,K as requestGrantSync,y as runtimeClient,H as runtimeId};
1
+ var w=Object.defineProperty;var r=(t,n)=>w(t,"name",{value:n,configurable:!0});import{createPrivateKey as v,randomUUID as E,sign as S}from"node:crypto";import{readFileSync as b}from"node:fs";import{join as j}from"node:path";import{crtrHome as x}from"../canvas/paths.js";import{isUnsandboxedMachine as g}from"../runtime/unsandboxed.js";import{emitEvent as f}from"../events/emit.js";import{allGrants as $,dependenciesOf as T,inferenceEndpointOf as A,replaceGrants as N}from"./mirror.js";import{onGrantSync as O}from"./on-sync.js";const H=300;function G(t,n){const e=Math.floor(Date.now()/1e3),s=Buffer.from(JSON.stringify({alg:"ES256",typ:"JWT",kid:t.kid})).toString("base64url"),c=Buffer.from(JSON.stringify({iss:t.client_id,sub:t.client_id,aud:n,iat:e,exp:e+300,jti:E()})).toString("base64url"),o=`${s}.${c}`,i=S("sha256",Buffer.from(o),{key:v({key:t.jwk,format:"jwk"}),dsaEncoding:"ieee-p1363"});return`${o}.${i.toString("base64url")}`}r(G,"assertion");function y(){const t=JSON.parse(b(j(x(),"secrets","runtime-client.json"),"utf8"));if(!t.jwk||t.alg!=="ES256"||!t.kid||!t.client_id||!t.runtime||t.jwk.kid!==t.kid||t.jwk.kty!=="EC"||t.jwk.crv!=="P-256"||!t.jwk.d)throw new TypeError("invalid runtime client key");return t}r(y,"runtimeClient");function K(t=y()){return t.runtime.startsWith("runtime:")?t.runtime.slice(8):t.runtime}r(K,"runtimeId");function R(t){return`This machine was removed from your Northlight account. To use it again, create a join token at ${t.replace(/\/$/,"")}/account/machines, and run \`crtr sys join\` from a plain terminal.`}r(R,"machineRevokedMessage");class h extends Error{static{r(this,"MachineRevokedError")}constructor(n){super(R(n)),this.name="MachineRevokedError"}}async function B(t,n){const e=y(),s=`${t.replace(/\/$/,"")}/token`,c=new URLSearchParams({grant_type:"client_credentials",client_id:e.client_id,client_assertion_type:"urn:ietf:params:oauth:client-assertion-type:jwt-bearer",client_assertion:G(e,s),...n===void 0?{}:{audience:n}}),o=await fetch(s,{method:"POST",body:c,signal:AbortSignal.timeout(1e4)});if(!o.ok)throw o.status===401&&g()&&await D(o)?new h(t):new Error(`directory token request failed: ${o.status}`);const i=await o.json();if(typeof i.access_token!="string")throw new TypeError("directory token response has no access_token");return{access_token:i.access_token,expires_at:J(i.access_token,i.expires_in)}}r(B,"mintDirectoryToken");async function D(t){try{return(await t.json()).error==="invalid_client"}catch{return!1}}r(D,"isInvalidClient");function J(t,n){if(typeof n=="number"&&Number.isFinite(n)&&n>0)return Date.now()+n*1e3;try{const e=JSON.parse(Buffer.from(t.split(".")[1]??"","base64url").toString("utf8"));if(typeof e.exp=="number")return e.exp*1e3}catch{}return Date.now()+3e5}r(J,"tokenExpiry");function M(t,n){if(!t||typeof t!="object")throw new TypeError("invalid grant sync response");const e=t,s=n.runtime.startsWith("runtime:")?n.runtime:`runtime:${n.runtime}`;if(!(e.runtime===s||g()&&typeof e.runtime=="string"&&e.runtime.startsWith("runtime:")&&e.grantee!=="app:terminal")||typeof e.id!="string"||typeof e.sub!="string"||typeof e.grantee!="string"||!Number.isInteger(e.rev)||!Array.isArray(e.scopes)||!Array.isArray(e.resolved_scopes)||!["active","revoked","removed"].includes(e.status??"")||typeof e.synced_at!="string")throw new TypeError("invalid grant sync response");const i=t.revisions?.find(a=>a.rev===e.rev)?.approved_at;if(typeof i!="string")throw new TypeError("grant revision has no approval time");const d=e.payer==="app"?"app":"user";return{...e,spend_cap:e.spend_cap??null,expires_at:e.expires_at??null,revoked_at:e.revoked_at??null,approved_at:i,payer:d,dependencies:T(e.dependencies),inference_endpoint:d==="app"?A(e.inference_endpoint):null,run_notice_url:typeof e.run_notice_url=="string"&&e.run_notice_url!==""?e.run_notice_url:null}}r(M,"responseGrant");let _;function V(t){return _?_(t):Promise.resolve()}r(V,"requestGrantSync");function Y(t,n=e=>{}){let e;const s=t.replace(/\/$/,""),c=r(async()=>{const a=y(),u=await B(s),l=await fetch(`${s}/v1/runtime/grants`,{headers:{Authorization:`Bearer ${u.access_token}`},signal:AbortSignal.timeout(1e4)});if(!l.ok)throw new Error(`directory grant sync failed: ${l.status}`);const p=await l.json();if(!Array.isArray(p.grants)||typeof p.synced_at!="string")throw new TypeError("invalid grant sync response");const k=$();N(p.grants.map(m=>M(m,a)),p.synced_at,s),f({level:"info",event:"daemon.grants.synced",fields:{grants:p.grants.length,synced_at:p.synced_at}});try{await O(k,n)}catch(m){f({level:"error",event:"daemon.grants.on_sync_failed",error:m})}},"run");let o;const i=r(()=>{const a=c().catch(u=>{throw u instanceof h?f({level:"error",event:"daemon.grants.machine_revoked",error:u}):f({level:"error",event:"daemon.grants.sync_failed",error:u}),u}).finally(()=>{e===a&&(e=void 0)});return e=a,a},"start"),d=r((a={})=>e?a.fresh?(o??=e.catch(()=>{}).then(()=>(o=void 0,e??i())),o):e:i(),"sync");return _=d,d}r(Y,"createGrantSync");export{H as GRANT_SYNC_INTERVAL_S,h as MachineRevokedError,G as assertion,Y as createGrantSync,R as machineRevokedMessage,B as mintDirectoryToken,V as requestGrantSync,y as runtimeClient,K as runtimeId};
@@ -0,0 +1,16 @@
1
+ import { type ModelStrength } from '../../types.js';
2
+ import { type InferenceEndpoint } from '../grants/mirror.js';
3
+ export interface AppRung {
4
+ strength: ModelStrength;
5
+ model: string;
6
+ }
7
+ /** The rung the endpoint serves at `strength`, or the nearest declared one (the stronger on a tie). */
8
+ export declare function rungAt(endpoint: InferenceEndpoint, strength: ModelStrength): AppRung;
9
+ /** The rung a request for `requested` lands on. An omitted model is the `strong` rung. An exact request for a
10
+ * concrete id outside the rungs is refused `substitution_refused`. */
11
+ export declare function resolveAppRung(endpoint: InferenceEndpoint, requested: string | undefined, exact?: boolean): AppRung;
12
+ /** Every rung in the order `llm` tries them: the requested rung first, then the declared rungs by distance. */
13
+ export declare function appCandidates(endpoint: InferenceEndpoint, requested: string | undefined, exact?: boolean): AppRung[];
14
+ /** The rung an app-paid grant's node runs on for `requested` (RT-RTE-24), or null when the grant is user-paid or
15
+ * absent. Every path that chooses a node's model (spawn, `node config --model`) resolves through here. */
16
+ export declare function appRungForGrant(grantId: string | null | undefined, requested: string | undefined, exact?: boolean): AppRung | null;
@@ -0,0 +1 @@
1
+ var h=Object.defineProperty;var f=(e,r)=>h(e,"name",{value:r,configurable:!0});import{defaultModelLaddersConfig as x}from"../../types.js";import{MODEL_STRENGTHS as b,stripThinkingSuffix as p}from"../runtime/model-selection.js";import{modelTokenStrength as O}from"../runtime/launch.js";import{RunError as u}from"../runs/operations.js";import{grantRecord as w}from"../grants/mirror.js";const o=b;function l(e,r){const t=o.indexOf(r);return o.filter(n=>e.rungs[n]!==void 0).sort((n,i)=>Math.abs(o.indexOf(n)-t)-Math.abs(o.indexOf(i)-t)||o.indexOf(n)-o.indexOf(i)).map(n=>({strength:n,model:e.rungs[n]}))}f(l,"rungsByDistance");function d(e,r){const t=l(e,r)[0];if(t===void 0)throw new u(404,"model_unavailable","the app's inference endpoint declares no rung");return t}f(d,"rungAt");function g(e,r,t=!1){if(r===void 0||r==="")return d(e,"strong");const n=O(r)??void 0;if(n!==void 0)return d(e,n);const i=p(r),s=o.find(a=>e.rungs[a]===i);if(s!==void 0)return{strength:s,model:e.rungs[s]};if(t)throw new u(400,"substitution_refused",`model '${r}' is not one of this app's inference rungs`);const c=x(),m=[...Object.entries(c.anthropic),...Object.entries(c.openai)].find(([,a])=>p(a)===i);return d(e,m?.[0]??"strong")}f(g,"resolveAppRung");function v(e,r,t=!1){const n=g(e,r,t);if(t)return[n];const i=l(e,n.strength).filter(s=>s.strength!==n.strength);return[n,...i]}f(v,"appCandidates");function G(e,r,t=!1){const n=e?w(e):null;if(n?.payer!=="app")return null;if(!n.inference_endpoint)throw new u(404,"model_unavailable",`${n.grantee} is app-paid but its grant carries no inference endpoint`);return g(n.inference_endpoint,r,t)}f(G,"appRungForGrant");export{v as appCandidates,G as appRungForGrant,g as resolveAppRung,d as rungAt};
@@ -1,5 +1,6 @@
1
1
  import { type ThinkingLevel } from '@earendil-works/pi-ai/compat';
2
2
  import { type SpendTarget } from '../usage/ledger.js';
3
+ import type { GrantRecord, InferenceEndpoint } from '../grants/mirror.js';
3
4
  type WireContent = string | null | Array<{
4
5
  type: 'text';
5
6
  text: string;
@@ -54,11 +55,19 @@ export interface LlmResult {
54
55
  completion: Record<string, unknown>;
55
56
  chunks: Array<Record<string, unknown>>;
56
57
  }
58
+ /** Who the call is made for: the caller's grant decides the payer (RT-RTE-12). A caller with no grant (the person) is user-paid. */
59
+ export interface LlmCaller {
60
+ payer: 'user' | 'app';
61
+ sub: string;
62
+ grantee: string;
63
+ inference_endpoint: InferenceEndpoint | null;
64
+ }
65
+ export declare function llmCallerOf(grant: Pick<GrantRecord, 'payer' | 'sub' | 'grantee' | 'inference_endpoint'> | null | undefined): LlmCaller | undefined;
57
66
  /** `spend`: the grantee a user-paid call bills and its grant's cap. The call is refused
58
67
  * `cap_exhausted` once the grantee's month spend reached the cap, and a completed call
59
68
  * is added to the grantee's month. Omitted, the call is neither capped nor counted. */
60
69
  export declare function runLlm(input: LlmRequest, signal?: AbortSignal, onChunk?: (chunk: Record<string, unknown>) => void, target?: {
61
70
  cwd: string;
62
71
  profileId: string | null;
63
- }, spend?: SpendTarget): Promise<LlmResult>;
72
+ }, spend?: SpendTarget, caller?: LlmCaller): Promise<LlmResult>;
64
73
  export {};
@@ -1,3 +1,3 @@
1
- var de=Object.defineProperty;var u=(t,e)=>de(t,"name",{value:e,configurable:!0});import{randomUUID as ce}from"node:crypto";import{streamSimple as fe}from"@earendil-works/pi-ai/compat";import{expandModelCandidates as Y,parseModelSpec as ue,probeRouteAvailability as me}from"../model-routes.js";import{readMergedLaunchConfig as ee}from"../config.js";import{openModelRegistry as pe}from"../runtime/model-registry.js";import{coolingUntilFor as te,isManagedProvider as E,markSubscriptionRateLimited as he,markSubscriptionSuccess as _e,readSubscriptionPool as ye,retryAfterMsFromHeaders as Z,retryAfterMsFromError as ge,refreshSubscriptionCredential as ve,subscriptionNeedsSuccessWrite as be}from"../subscription-state.js";import{classifyRateLimit as we,describeRateLimitScope as oe,rateLimitHeaders as xe}from"../rate-limit-scope.js";import{RunError as l}from"../runs/operations.js";import{recognizeProviderError as ne}from"../provider-error-recognition.js";import{isSafetyRefusal as ke}from"./refusal.js";import{assertUnderCap as je,recordModelUsage as qe}from"../usage/ledger.js";function Ae(t){return typeof t=="string"||t===null||Array.isArray(t)&&t.every(e=>e&&typeof e=="object"&&(e.type==="text"&&typeof e.text=="string"||e.type==="image_url"&&typeof e.image_url?.url=="string"&&/^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(e.image_url.url)))}u(Ae,"validContent");function re(t){return Array.isArray(t)?t.map(e=>{if(e.type==="text")return e;const[,n,r]=/^data:(image\/(?:png|jpeg|gif|webp));base64,(.*)$/.exec(e.image_url.url);return{type:"image",mimeType:n,data:r}}):t??""}u(re,"imageContent");function We(t){if(!t||typeof t!="object"||Array.isArray(t))throw new l(400,"invalid_request","body must be an object");const e=t;if(!Array.isArray(e.messages)||!e.messages.every(n=>n&&typeof n=="object"&&["system","user","assistant","tool"].includes(n.role)&&Ae(n.content)))throw new l(400,"invalid_request","messages must be chat messages","messages");if(e.messages.some(n=>["system","assistant"].includes(n.role)&&typeof n.content!="string"&&n.content!==null))throw new l(400,"invalid_request","system and assistant content must be text","messages");if(e.model!==void 0&&typeof e.model!="string")throw new l(400,"invalid_request","model must be a string","model");if(e.model_policy!==void 0&&!["exact","substitute"].includes(e.model_policy))throw new l(400,"invalid_request","invalid model_policy","model_policy");if(e.payer!==void 0&&!["user","app"].includes(e.payer))throw new l(400,"invalid_request","invalid payer","payer");if(e.reasoning_effort!==void 0&&!["minimal","low","medium","high","xhigh","max"].includes(e.reasoning_effort))throw new l(400,"invalid_request","invalid reasoning_effort","reasoning_effort");if(e.stream!==void 0&&typeof e.stream!="boolean")throw new l(400,"invalid_request","stream must be boolean","stream");if(e.tools!==void 0&&(!Array.isArray(e.tools)||!e.tools.every(n=>n?.type==="function"&&typeof n.function?.name=="string"&&n.function.name.length>0&&(n.function.description===void 0||typeof n.function.description=="string")&&(n.function.parameters===void 0||n.function.parameters!==null&&typeof n.function.parameters=="object"&&!Array.isArray(n.function.parameters)&&n.function.parameters.type==="object"))))throw new l(400,"invalid_request","invalid tools","tools");if(e.tool_choice!==void 0&&e.tool_choice!=="auto"&&e.tool_choice!=="none"&&e.tool_choice!=="required"&&(typeof e.tool_choice!="object"||e.tool_choice===null||e.tool_choice.type!=="function"||typeof e.tool_choice.function?.name!="string"))throw new l(400,"invalid_request","invalid tool_choice","tool_choice");if(e.messages.some(n=>n.role==="assistant"&&n.tool_calls!==void 0&&(!Array.isArray(n.tool_calls)||!n.tool_calls.every(r=>r&&typeof r.id=="string"&&typeof r.function?.name=="string"&&typeof r.function.arguments=="string"&&Me(r.function.arguments)))))throw new l(400,"invalid_request","invalid assistant tool_calls","messages");if(e.stop!==void 0&&!(typeof e.stop=="string"||Array.isArray(e.stop)&&e.stop.every(n=>typeof n=="string")))throw new l(400,"invalid_request","invalid stop","stop");if(e.seed!==void 0&&!Number.isSafeInteger(e.seed))throw new l(400,"invalid_request","invalid seed","seed");for(const[n,r]of[["temperature",e.temperature],["top_p",e.top_p],["max_tokens",e.max_tokens]])if(r!==void 0&&(typeof r!="number"||!Number.isFinite(r)||n==="temperature"&&(r<0||r>2)||n==="top_p"&&(r<0||r>1)||n==="max_tokens"&&(!Number.isSafeInteger(r)||r<1)))throw new l(400,"invalid_request",`invalid ${n}`,n);if(e.response_format!==void 0){const n=e.response_format;if(!n||typeof n!="object"||Array.isArray(n)||!["text","json_object","json_schema"].includes(n.type)||n.type==="json_schema"&&(typeof n.json_schema?.name!="string"||!n.json_schema.name||!n.json_schema.schema||typeof n.json_schema.schema!="object"||Array.isArray(n.json_schema.schema)))throw new l(400,"invalid_request","invalid response_format","response_format")}if(e.metadata!==void 0&&Buffer.byteLength(JSON.stringify(e.metadata)??"")>4096)throw new l(400,"invalid_request","metadata exceeds 4 KB","metadata");return e}u(We,"parseLlmRequest");function Me(t){try{const e=JSON.parse(t);return e!==null&&typeof e=="object"&&!Array.isArray(e)}catch{return!1}}u(Me,"isJsonObject");function Re(t){const e=Date.now(),n=t.messages.map(a=>a.role==="system"?{role:"system",content:a.content??"",timestamp:e}:a.role==="tool"?{role:"toolResult",toolCallId:a.tool_call_id??"",toolName:"",content:typeof a.content=="string"||a.content===null?[{type:"text",text:a.content??""}]:re(a.content),isError:!1,timestamp:e}:a.role==="assistant"?{role:"assistant",content:[...a.content?[{type:"text",text:a.content}]:[],...(a.tool_calls??[]).map(w=>({type:"toolCall",id:w.id,name:w.function.name,arguments:JSON.parse(w.function.arguments)}))],api:"openai-completions",provider:"openai",model:t.model??"",usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"stop",timestamp:e}:{role:"user",content:re(a.content),timestamp:e}),r=n.findIndex(a=>a.role!=="system"),y=n.slice(0,r<0?n.length:r).map(a=>a.content),m=y.join(`
1
+ var ye=Object.defineProperty;var u=(e,t)=>ye(e,"name",{value:t,configurable:!0});import{randomUUID as ge}from"node:crypto";import{streamSimple as be}from"@earendil-works/pi-ai/compat";import{expandModelCandidates as re,parseModelSpec as se,probeRouteAvailability as ve}from"../model-routes.js";import{readMergedLaunchConfig as ie}from"../config.js";import{openModelRegistry as we}from"../runtime/model-registry.js";import{coolingUntilFor as ae,isManagedProvider as H,markSubscriptionRateLimited as xe,markSubscriptionSuccess as ke,readSubscriptionPool as je,retryAfterMsFromHeaders as K,retryAfterMsFromError as qe,refreshSubscriptionCredential as Ae,subscriptionNeedsSuccessWrite as Me}from"../subscription-state.js";import{classifyRateLimit as $e,describeRateLimitScope as de,rateLimitHeaders as Re}from"../rate-limit-scope.js";import{RunError as s}from"../runs/operations.js";import{recognizeProviderError as le}from"../provider-error-recognition.js";import{isSafetyRefusal as Ie}from"./refusal.js";import{assertUnderCap as Se,recordModelUsage as ce}from"../usage/ledger.js";import{outboundToken as Ce}from"../providers/mint.js";import{discardFile as Le}from"../providers/token-cache.js";import{ProviderError as Oe}from"../providers/errors.js";import{appCandidates as Fe}from"./app-rungs.js";function Ne(e){return typeof e=="string"||e===null||Array.isArray(e)&&e.every(t=>t&&typeof t=="object"&&(t.type==="text"&&typeof t.text=="string"||t.type==="image_url"&&typeof t.image_url?.url=="string"&&/^data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(t.image_url.url)))}u(Ne,"validContent");function ue(e){return Array.isArray(e)?e.map(t=>{if(t.type==="text")return t;const[,n,r]=/^data:(image\/(?:png|jpeg|gif|webp));base64,(.*)$/.exec(t.image_url.url);return{type:"image",mimeType:n,data:r}}):e??""}u(ue,"imageContent");function rt(e){if(!e||typeof e!="object"||Array.isArray(e))throw new s(400,"invalid_request","body must be an object");const t=e;if(!Array.isArray(t.messages)||!t.messages.every(n=>n&&typeof n=="object"&&["system","user","assistant","tool"].includes(n.role)&&Ne(n.content)))throw new s(400,"invalid_request","messages must be chat messages","messages");if(t.messages.some(n=>["system","assistant"].includes(n.role)&&typeof n.content!="string"&&n.content!==null))throw new s(400,"invalid_request","system and assistant content must be text","messages");if(t.model!==void 0&&typeof t.model!="string")throw new s(400,"invalid_request","model must be a string","model");if(t.model_policy!==void 0&&!["exact","substitute"].includes(t.model_policy))throw new s(400,"invalid_request","invalid model_policy","model_policy");if(t.payer!==void 0&&!["user","app"].includes(t.payer))throw new s(400,"invalid_request","invalid payer","payer");if(t.reasoning_effort!==void 0&&!["minimal","low","medium","high","xhigh","max"].includes(t.reasoning_effort))throw new s(400,"invalid_request","invalid reasoning_effort","reasoning_effort");if(t.stream!==void 0&&typeof t.stream!="boolean")throw new s(400,"invalid_request","stream must be boolean","stream");if(t.tools!==void 0&&(!Array.isArray(t.tools)||!t.tools.every(n=>n?.type==="function"&&typeof n.function?.name=="string"&&n.function.name.length>0&&(n.function.description===void 0||typeof n.function.description=="string")&&(n.function.parameters===void 0||n.function.parameters!==null&&typeof n.function.parameters=="object"&&!Array.isArray(n.function.parameters)&&n.function.parameters.type==="object"))))throw new s(400,"invalid_request","invalid tools","tools");if(t.tool_choice!==void 0&&t.tool_choice!=="auto"&&t.tool_choice!=="none"&&t.tool_choice!=="required"&&(typeof t.tool_choice!="object"||t.tool_choice===null||t.tool_choice.type!=="function"||typeof t.tool_choice.function?.name!="string"))throw new s(400,"invalid_request","invalid tool_choice","tool_choice");if(t.messages.some(n=>n.role==="assistant"&&n.tool_calls!==void 0&&(!Array.isArray(n.tool_calls)||!n.tool_calls.every(r=>r&&typeof r.id=="string"&&typeof r.function?.name=="string"&&typeof r.function.arguments=="string"&&De(r.function.arguments)))))throw new s(400,"invalid_request","invalid assistant tool_calls","messages");if(t.stop!==void 0&&!(typeof t.stop=="string"||Array.isArray(t.stop)&&t.stop.every(n=>typeof n=="string")))throw new s(400,"invalid_request","invalid stop","stop");if(t.seed!==void 0&&!Number.isSafeInteger(t.seed))throw new s(400,"invalid_request","invalid seed","seed");for(const[n,r]of[["temperature",t.temperature],["top_p",t.top_p],["max_tokens",t.max_tokens]])if(r!==void 0&&(typeof r!="number"||!Number.isFinite(r)||n==="temperature"&&(r<0||r>2)||n==="top_p"&&(r<0||r>1)||n==="max_tokens"&&(!Number.isSafeInteger(r)||r<1)))throw new s(400,"invalid_request",`invalid ${n}`,n);if(t.response_format!==void 0){const n=t.response_format;if(!n||typeof n!="object"||Array.isArray(n)||!["text","json_object","json_schema"].includes(n.type)||n.type==="json_schema"&&(typeof n.json_schema?.name!="string"||!n.json_schema.name||!n.json_schema.schema||typeof n.json_schema.schema!="object"||Array.isArray(n.json_schema.schema)))throw new s(400,"invalid_request","invalid response_format","response_format")}if(t.metadata!==void 0&&Buffer.byteLength(JSON.stringify(t.metadata)??"")>4096)throw new s(400,"invalid_request","metadata exceeds 4 KB","metadata");return t}u(rt,"parseLlmRequest");function De(e){try{const t=JSON.parse(e);return t!==null&&typeof t=="object"&&!Array.isArray(t)}catch{return!1}}u(De,"isJsonObject");function Pe(e){const t=Date.now(),n=e.messages.map(i=>i.role==="system"?{role:"system",content:i.content??"",timestamp:t}:i.role==="tool"?{role:"toolResult",toolCallId:i.tool_call_id??"",toolName:"",content:typeof i.content=="string"||i.content===null?[{type:"text",text:i.content??""}]:ue(i.content),isError:!1,timestamp:t}:i.role==="assistant"?{role:"assistant",content:[...i.content?[{type:"text",text:i.content}]:[],...(i.tool_calls??[]).map(g=>({type:"toolCall",id:g.id,name:g.function.name,arguments:JSON.parse(g.function.arguments)}))],api:"openai-completions",provider:"openai",model:e.model??"",usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"stop",timestamp:t}:{role:"user",content:ue(i.content),timestamp:t}),r=n.findIndex(i=>i.role!=="system"),w=n.slice(0,r<0?n.length:r).map(i=>i.content),y=w.join(`
2
2
 
3
- `),p=t.tools?.map(({function:a})=>({name:a.name,description:a.description??"",parameters:a.parameters??{type:"object",properties:{}}}));return{...m?{systemPrompt:m}:{},messages:n.slice(y.length),...p?{tools:p}:{}}}u(Re,"contextOf");function se(t,e,n,r){if(!t){const m=ee(n,r),p=Object.values({...m.modelRoutes??{},...m.legacyModelRoutes??{}}),a=p.length?[m.modelLadders.defaultProvider,...m.modelRouting?.familyOrder??[],...p.map(w=>w.family)].find(w=>p.some(I=>I.family===w)):m.modelLadders.defaultProvider??"anthropic";return{kind:"logical",family:a,strength:"strong",spec:`${a}/strong`}}const y=/^([^/]+)\/(ultra|strong|medium|light)$/.exec(t);return y?{kind:"logical",family:y[1],strength:y[2],spec:t}:{kind:e==="exact"?"exact":"concrete",spec:t}}u(se,"requestFor");function $e(t,e){const{input:n,output:r,cacheRead:y,cacheWrite:m}=t.cost;return n===0&&r===0&&y===0&&m===0?null:Number.isFinite(e)&&e>=0?e:null}u($e,"usdEquivalent");function Ie(t,e){return t.compat?.allowedFallbackModels?.find(r=>r.provider===t.provider&&r.model===e)}u(Ie,"allowedFallback");async function He(t,e,n,r,y){if(t.payer==="app")throw new l(403,"payer_not_app","app-paid inference routes are not registered");je(y);const m=await pe(),p=se(t.model,t.model_policy,r?.cwd,r?.profileId),a=u(f=>f.routeId!=="concrete"&&f.routeId!=="exact","configured"),w=u(f=>{const s=m.find(f.providerId,f.modelId);return s!==void 0&&(!t.reasoning_effort||s.reasoning)&&(t.seed===void 0&&t.response_format===void 0||s.api==="openai-completions")&&(t.stop===void 0||["openai-completions","anthropic-messages"].includes(s.api))},"matchesFeatures"),I=Y(p.kind==="exact"?{kind:"concrete",spec:p.spec}:p,r?.cwd,r?.profileId).filter(a);let x;if(p.kind==="logical")x=t.model_policy==="exact"?I.filter(f=>f.family===p.family&&f.strength===p.strength).slice(0,1):I;else{const f=ue(p.spec),s=I.filter(c=>c.providerId===f?.providerId&&c.modelId===f.modelId&&(t.model_policy!=="exact"||c.thinkingLevel===f.thinkingLevel)&&w(c));if(t.model_policy==="exact")x=s.slice(0,1);else if(s.length)x=s;else{const c=ee(r?.cwd,r?.profileId),q=[...Object.values(c.modelRoutes??{}).flatMap(i=>Object.entries(i.models)),...[c.modelLadders.anthropic,c.modelLadders.openai].flatMap(i=>Object.entries(i))].find(([,i])=>i===p.spec)?.[0],L=se(void 0,"substitute",r?.cwd,r?.profileId);x=q&&L.kind==="logical"?Y({...L,strength:q,spec:p.spec},r?.cwd,r?.profileId).filter(i=>a(i)&&i.strength===q):[]}}if(x=x.filter(w),x.length===0)throw t.model_policy==="exact"?new l(400,"substitution_refused","no eligible model route is configured"):new l(404,"model_unavailable","no eligible model route is configured");const G=`chatcmpl-${ce()}`;let j=1/0,_,J;for(const f of x){if(!me(f,m).authenticated)continue;const s=m.find(f.providerId,f.modelId);if(!s)continue;const c=f.providerId,S=E(c)?ye(c).filter(i=>!i.authFailure):[],q=S.filter(i=>te(i,s.id)<=Date.now());if(S.length&&!q.length){const i=Math.min(...S.map(A=>te(A,s.id)));i<j&&(j=i,J=`${s.provider}/${s.id}`);continue}const L=S.length?q:[void 0];for(const i of L){const A=Date.now();let Q=i?.access;if(i&&i.expires<=Date.now()&&E(c)){const o=m.getProvider(c)?.auth.oauth;if(!o)throw new l(503,"upstream_model_unavailable",`no OAuth refresh for ${c}`,void 0,{served_model:`${s.provider}/${s.id}`});Q=(await ve(c,i.label,async(g,X)=>{const P=await o.refresh({type:"oauth",refresh:g,access:"",expires:0,...X?{clientId:X}:{}},e??new AbortController().signal),B=P.clientId;return{refresh:P.refresh,access:P.access,expires:P.expires,...typeof B=="string"&&B?{clientId:B}:{}}})).access}const ie=Re(t),M=i?void 0:await m.getApiKeyAndHeaders(s);if(M&&!M.ok)continue;const v=`${s.provider}/${s.id}`,R=t.model??v;let k=!1,V,b=3e4,$;const U=[],T=[],O=[];let d;const N=u((o,h=null)=>({id:G,object:"chat.completion.chunk",created:Math.floor(A/1e3),model:v,served_model:v,requested_model:R,substituted:R!==v&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test(R),choices:[{index:0,delta:o,finish_reason:h}]}),"chunk"),D=u(o=>{U.push(o),n?.(o)},"emit"),ae=fe(s,ie,{apiKey:Q??(M?.ok?M.apiKey:void 0),...M?.ok?{headers:M.headers}:{},signal:e,reasoning:t.reasoning_effort??f.thinkingLevel,...t.temperature!==void 0?{temperature:t.temperature}:{},...t.max_tokens!==void 0?{maxTokens:t.max_tokens}:{},...t.top_p!==void 0?{samplingParams:{top_p:t.top_p}}:{},...t.tool_choice==="none"?{toolChoice:"none"}:{},onPayload:u(o=>{if(!o||typeof o!="object")return;const h=t.tool_choice,g=h==="required"?s.api==="anthropic-messages"?{type:"any"}:"required":typeof h=="object"&&h!==null?s.api==="anthropic-messages"?{type:"tool",name:h.function.name}:h:void 0;return{...o,...g?{tool_choice:g}:{},...t.stop!==void 0?{[s.api==="anthropic-messages"?"stop_sequences":"stop"]:typeof t.stop=="string"?[t.stop]:t.stop}:{},...t.seed!==void 0?{seed:t.seed}:{},...t.response_format!==void 0?{response_format:t.response_format}:{}}},"onPayload"),fetch:u(async(o,h)=>{const g=await fetch(o,h);return V=g.status,g.status===429&&(k=!0,$=g.headers,b=Z(g.headers)??15e3),g},"fetch"),onResponse:u(o=>{o.status===429&&(k=!0,$=o.headers,b=Z(o.headers)??15e3)},"onResponse")});try{for await(const o of ae)o.type==="text_delta"&&(T.push(o.delta),D(N({content:o.delta}))),o.type==="thinking_delta"&&(O.push(o.delta),D(N({reasoning_content:o.delta}))),o.type==="done"&&(d=o.message),o.type==="error"&&(d=o.error)}catch(o){const h=ge(o);$===void 0&&o&&typeof o=="object"&&"headers"in o&&Object.keys(xe(o.headers)).length&&($=o.headers);const g=ne({status:o&&typeof o=="object"&&"status"in o?o.status:void 0,message:o instanceof Error?o.message:String(o)});if(!k&&g.kind!=="rate-limit")throw U.length?new l(503,"upstream_model_unavailable",o instanceof Error?o.message:String(o),void 0,{served_model:v}):o;k=!0,b=h??b}if(!k&&d?.stopReason==="error"&&ne({message:d.errorMessage}).kind==="rate-limit"&&(k=!0,b=15e3),k){const o=we($,s.id);if(Z($)===void 0&&o.resetAt!==void 0&&(b=Math.max(b,o.resetAt-Date.now())),_={provider:c,model:s.id,...i?{account:i.label}:{},observation:o},console.error(`[llm] 429 from ${v}${i?` account "${i.label}"`:""}: scope=${oe(o.scope)} retry_after_ms=${b} headers=${JSON.stringify(o.headers)}`),i&&E(c)&&await he(c,i.label,b,A,Date.now(),o.scope),Date.now()+b<j&&(j=Date.now()+b,J=v),T.length||O.length)throw new l(503,"service_unavailable","vendor rate limit after output began",void 0,{served_model:v});continue}const F=ke(d);if(V===400&&t.tool_choice!==void 0&&t.tool_choice!=="auto"&&t.tool_choice!=="none"&&/\btool[_ ]choice\b/i.test(d?.errorMessage??"")&&/not supported|does not support|unsupported|cannot|must be auto/i.test(d?.errorMessage??""))throw new l(400,"invalid_request",d.errorMessage,"tool_choice",{served_model:v});if(!d||d.stopReason==="error"&&!F||d.stopReason==="aborted")throw new l(503,"upstream_model_unavailable",d?.errorMessage??"model request failed",void 0,{served_model:v});i&&E(c)&&be(i,s.id)&&await _e(c,i.label,A,s.id);const W=d.content.filter(o=>o.type==="toolCall").map(o=>({id:o.id,type:"function",function:{name:o.name,arguments:JSON.stringify(o.arguments)}})),H=F?"content_filter":d.stopReason==="toolUse"?"tool_calls":d.stopReason==="length"?"length":"stop",K=d.responseModel?Ie(s,d.responseModel):void 0,C=K?`${s.provider}/${K.model}`:v,z={prompt_tokens:d.usage.input,completion_tokens:d.usage.output,total_tokens:d.usage.totalTokens,cache_read_tokens:d.usage.cacheRead,cache_write_tokens:d.usage.cacheWrite,usd_equivalent:$e(K??s,d.usage.cost.total),served_model:C};y&&qe(y.grantee,"user",C,d.usage,z.usd_equivalent);const le={id:G,object:"chat.completion",created:Math.floor(A/1e3),model:C,served_model:C,requested_model:R,substituted:R!==C&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test(R),choices:[{index:0,message:{role:"assistant",content:T.join("")||null,...O.length?{reasoning_content:O.join("")}:{},...W.length?{tool_calls:W}:{},...F?{refusal:d.errorMessage??"Content filtered"}:{}},finish_reason:H}],usage:z,...t.metadata!==void 0?{metadata:t.metadata}:{}};for(const[o,h]of W.entries())D(N({tool_calls:[{index:o,...h}]}));return D({...N({},H),...F?{choices:[{index:0,delta:{},finish_reason:H,message:{refusal:d.errorMessage??"Content filtered"}}]}:{},usage:z}),{completion:le,chunks:U}}}throw j!==1/0?new l(429,"all_routes_exhausted","all eligible model routes are rate-limited",void 0,{retry_after_s:Math.max(1,Math.ceil((j-Date.now())/1e3)),served_model:_?`${_.provider}/${_.model}`:J,..._?{rate_limit:{provider:_.provider,model:_.model,..._.account?{account:_.account}:{},scope:oe(_.observation.scope),..._.observation.claim?{claim:_.observation.claim}:{},headers:_.observation.headers}}:{}},void 0,!0):t.model_policy==="exact"?new l(400,"substitution_refused","the requested model is unavailable"):new l(404,"model_unavailable","no usable model route is available")}u(He,"runLlm");export{We as parseLlmRequest,He as runLlm};
3
+ `),l=e.tools?.map(({function:i})=>({name:i.name,description:i.description??"",parameters:i.parameters??{type:"object",properties:{}}}));return{...y?{systemPrompt:y}:{},messages:n.slice(w.length),...l?{tools:l}:{}}}u(Pe,"contextOf");function fe(e,t,n,r){if(!e){const y=ie(n,r),l=Object.values({...y.modelRoutes??{},...y.legacyModelRoutes??{}}),i=l.length?[y.modelLadders.defaultProvider,...y.modelRouting?.familyOrder??[],...l.map(g=>g.family)].find(g=>l.some(F=>F.family===g)):y.modelLadders.defaultProvider??"anthropic";return{kind:"logical",family:i,strength:"strong",spec:`${i}/strong`}}const w=/^([^/]+)\/(ultra|strong|medium|light)$/.exec(e);return w?{kind:"logical",family:w[1],strength:w[2],spec:e}:{kind:t==="exact"?"exact":"concrete",spec:e}}u(fe,"requestFor");function Ee(e,t){const{input:n,output:r,cacheRead:w,cacheWrite:y}=e.cost;return n===0&&r===0&&w===0&&y===0?null:Number.isFinite(t)&&t>=0?t:null}u(Ee,"usdEquivalent");function Te(e,t){return e.compat?.allowedFallbackModels?.find(r=>r.provider===e.provider&&r.model===t)}u(Te,"allowedFallback");function st(e){return e?{payer:e.payer??"user",sub:e.sub,grantee:e.grantee,inference_endpoint:e.inference_endpoint??null}:void 0}u(st,"llmCallerOf");function Ue(e){return e instanceof Oe?e.code==="server_error"?new s(503,"upstream_model_unavailable",e.message):new s(404,"model_unavailable",e.message):e}u(Ue,"mintFailure");async function it(e,t,n,r,w,y){const l=y?.payer==="app"?y:void 0;if(e.payer==="app"&&!l)throw new s(403,"payer_not_app","this grant is not app-paid");if(e.payer==="user"&&l)throw new s(400,"invalid_request","this grant is app-paid; payer cannot be user","payer");if(l&&!l.inference_endpoint)throw new s(404,"model_unavailable",`${l.grantee} is app-paid but its grant carries no inference endpoint`);const i=l?.inference_endpoint??void 0;l||Se(w);const g=await we(),F=u(c=>c.routeId!=="concrete"&&c.routeId!=="exact","configured"),Y=u(c=>{const p=g.find(c.providerId,c.modelId);return p!==void 0&&(!e.reasoning_effort||p.reasoning)&&(e.seed===void 0&&e.response_format===void 0||p.api==="openai-completions")&&(e.stop===void 0||["openai-completions","anthropic-messages"].includes(p.api))},"matchesFeatures"),me=u(()=>{const c=fe(e.model,e.model_policy,r?.cwd,r?.profileId),p=re(c.kind==="exact"?{kind:"concrete",spec:c.spec}:c,r?.cwd,r?.profileId).filter(F);let a;if(c.kind==="logical")a=e.model_policy==="exact"?p.filter(m=>m.family===c.family&&m.strength===c.strength).slice(0,1):p;else{const m=se(c.spec),M=p.filter(j=>j.providerId===m?.providerId&&j.modelId===m.modelId&&(e.model_policy!=="exact"||j.thinkingLevel===m.thinkingLevel)&&Y(j));if(e.model_policy==="exact")a=M.slice(0,1);else if(M.length)a=M;else{const j=ie(r?.cwd,r?.profileId),x=[...Object.values(j.modelRoutes??{}).flatMap(v=>Object.entries(v.models)),...[j.modelLadders.anthropic,j.modelLadders.openai].flatMap(v=>Object.entries(v))].find(([,v])=>v===c.spec)?.[0],f=fe(void 0,"substitute",r?.cwd,r?.profileId);a=x&&f.kind==="logical"?re({...f,strength:x,spec:c.spec},r?.cwd,r?.profileId).filter(v=>F(v)&&v.strength===x):[]}}return a},"ownerCandidates");let R;if(i?R=Fe(i,e.model,e.model_policy==="exact").flatMap((c,p)=>{const a=se(c.model);return a?[{routeId:"app",credentialSource:"app",family:"app",strength:c.strength,...a,rank:p}]:[]}):R=me(),R=R.filter(Y),R.length===0)throw e.model_policy==="exact"?new s(400,"substitution_refused","no eligible model route is configured"):new s(404,"model_unavailable","no eligible model route is configured");const ee=`chatcmpl-${ge()}`;let I=1/0,b,z;for(const c of R){if(!l&&!ve(c,g).authenticated)continue;const p=g.find(c.providerId,c.modelId);if(!p)continue;const a=i?{...p,baseUrl:`${i.base_url}/${p.provider}`}:p,m=c.providerId,M=!l&&H(m)?je(m).filter(x=>!x.authFailure):[],j=M.filter(x=>ae(x,a.id)<=Date.now());if(M.length&&!j.length){const x=Math.min(...M.map(f=>ae(f,a.id)));x<I&&(I=x,z=`${a.provider}/${a.id}`);continue}const te=l?[void 0,void 0]:M.length?j:[void 0];for(const[x,f]of te.entries()){const v=Date.now();let oe=f?.access;if(f&&f.expires<=Date.now()&&H(m)){const o=g.getProvider(m)?.auth.oauth;if(!o)throw new s(503,"upstream_model_unavailable",`no OAuth refresh for ${m}`,void 0,{served_model:`${a.provider}/${a.id}`});oe=(await Ae(m,f.label,async(k,ne)=>{const W=await o.refresh({type:"oauth",refresh:k,access:"",expires:0,...ne?{clientId:ne}:{}},t??new AbortController().signal),X=W.clientId;return{refresh:W.refresh,access:W.access,expires:W.expires,...typeof X=="string"&&X?{clientId:X}:{}}})).access}const pe=Pe(e);let N;if(l&&i)try{N=await Ce({audience:i.base_url,sub:l.sub,grantee:l.grantee,initiating_app:l.grantee,scope:["crtr:llm"]},{signal:t})}catch(o){throw Ue(o)}const S=f||l?void 0:await g.getApiKeyAndHeaders(a);if(S&&!S.ok)continue;const h=`${a.provider}/${a.id}`,C=e.model??h;let A=!1,O,q=3e4,$;const B=[],Z=[],D=[];let d,P;const E=u((o,_=null)=>({id:ee,object:"chat.completion.chunk",created:Math.floor(v/1e3),model:h,served_model:h,requested_model:C,substituted:C!==h&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test(C),choices:[{index:0,delta:o,finish_reason:_}]}),"chunk"),T=u(o=>{B.push(o),n?.(o)},"emit"),he=be(a,pe,{apiKey:N?.access_token??oe??(S?.ok?S.apiKey:void 0),...S?.ok?{headers:S.headers}:{},signal:t,reasoning:e.reasoning_effort??c.thinkingLevel,...e.temperature!==void 0?{temperature:e.temperature}:{},...e.max_tokens!==void 0?{maxTokens:e.max_tokens}:{},...e.top_p!==void 0?{samplingParams:{top_p:e.top_p}}:{},...e.tool_choice==="none"?{toolChoice:"none"}:{},onPayload:u(o=>{if(!o||typeof o!="object")return;const _=e.tool_choice,k=_==="required"?a.api==="anthropic-messages"?{type:"any"}:"required":typeof _=="object"&&_!==null?a.api==="anthropic-messages"?{type:"tool",name:_.function.name}:_:void 0;return{...o,...k?{tool_choice:k}:{},...e.stop!==void 0?{[a.api==="anthropic-messages"?"stop_sequences":"stop"]:typeof e.stop=="string"?[e.stop]:e.stop}:{},...e.seed!==void 0?{seed:e.seed}:{},...e.response_format!==void 0?{response_format:e.response_format}:{}}},"onPayload"),fetch:u(async(o,_)=>{const k=await fetch(o,_);return O=k.status,k.status===429&&(A=!0,$=k.headers,q=K(k.headers)??15e3),k},"fetch"),onResponse:u(o=>{o.status===429&&(A=!0,$=o.headers,q=K(o.headers)??15e3)},"onResponse")});try{for await(const o of he)o.type==="text_delta"&&(Z.push(o.delta),T(E({content:o.delta}))),o.type==="thinking_delta"&&(D.push(o.delta),T(E({reasoning_content:o.delta}))),o.type==="done"&&(d=o.message),o.type==="error"&&(d=o.error)}catch(o){const _=qe(o);$===void 0&&o&&typeof o=="object"&&"headers"in o&&Object.keys(Re(o.headers)).length&&($=o.headers);const k=le({status:o&&typeof o=="object"&&"status"in o?o.status:void 0,message:o instanceof Error?o.message:String(o)});if(l)P=o;else{if(!A&&k.kind!=="rate-limit")throw B.length?new s(503,"upstream_model_unavailable",o instanceof Error?o.message:String(o),void 0,{served_model:h}):o;A=!0,q=_??q}}if(l){if(O===401){if(N&&Le(N.key),x===0)continue;throw new s(404,"model_unavailable",`${i.base_url} refuses this runtime's tokens`,void 0,{served_model:h})}if(A&&K($)===void 0)throw new s(503,"upstream_model_unavailable",`${h} was rate-limited without a retry-after`,void 0,{served_model:h});if(!A&&(P!==void 0||O!==void 0&&O>=500))throw new s(503,"upstream_model_unavailable",P instanceof Error?P.message:d?.errorMessage??"inference endpoint failed",void 0,{served_model:h})}if(!A&&d?.stopReason==="error"&&le({message:d.errorMessage}).kind==="rate-limit"&&(A=!0,q=15e3),A){const o=$e($,a.id);if(K($)===void 0&&o.resetAt!==void 0&&(q=Math.max(q,o.resetAt-Date.now())),b={provider:m,model:a.id,...f?{account:f.label}:{},observation:o},console.error(`[llm] 429 from ${h}${f?` account "${f.label}"`:""}: scope=${de(o.scope)} retry_after_ms=${q} headers=${JSON.stringify(o.headers)}`),f&&H(m)&&await xe(m,f.label,q,v,Date.now(),o.scope),Date.now()+q<I&&(I=Date.now()+q,z=h),Z.length||D.length)throw new s(503,"service_unavailable","vendor rate limit after output began",void 0,{served_model:h});if(l)break;continue}const U=Ie(d);if(O===400&&e.tool_choice!==void 0&&e.tool_choice!=="auto"&&e.tool_choice!=="none"&&/\btool[_ ]choice\b/i.test(d?.errorMessage??"")&&/not supported|does not support|unsupported|cannot|must be auto/i.test(d?.errorMessage??""))throw new s(400,"invalid_request",d.errorMessage,"tool_choice",{served_model:h});if(!d||d.stopReason==="error"&&!U||d.stopReason==="aborted")throw new s(503,"upstream_model_unavailable",d?.errorMessage??"model request failed",void 0,{served_model:h});f&&H(m)&&Me(f,a.id)&&await ke(m,f.label,v,a.id);const G=d.content.filter(o=>o.type==="toolCall").map(o=>({id:o.id,type:"function",function:{name:o.name,arguments:JSON.stringify(o.arguments)}})),Q=U?"content_filter":d.stopReason==="toolUse"?"tool_calls":d.stopReason==="length"?"length":"stop",V=d.responseModel?Te(a,d.responseModel):void 0,L=V?`${a.provider}/${V.model}`:h,J={prompt_tokens:d.usage.input,completion_tokens:d.usage.output,total_tokens:d.usage.totalTokens,cache_read_tokens:d.usage.cacheRead,cache_write_tokens:d.usage.cacheWrite,usd_equivalent:Ee(V??a,d.usage.cost.total),served_model:L};l?ce(l.grantee,"app",L,d.usage,J.usd_equivalent):w&&ce(w.grantee,"user",L,d.usage,J.usd_equivalent);const _e={id:ee,object:"chat.completion",created:Math.floor(v/1e3),model:L,served_model:L,requested_model:C,substituted:C!==L&&!/^([^/]+)\/(ultra|strong|medium|light)$/.test(C),choices:[{index:0,message:{role:"assistant",content:Z.join("")||null,...D.length?{reasoning_content:D.join("")}:{},...G.length?{tool_calls:G}:{},...U?{refusal:d.errorMessage??"Content filtered"}:{}},finish_reason:Q}],usage:J,...e.metadata!==void 0?{metadata:e.metadata}:{}};for(const[o,_]of G.entries())T(E({tool_calls:[{index:o,..._}]}));return T({...E({},Q),...U?{choices:[{index:0,delta:{},finish_reason:Q,message:{refusal:d.errorMessage??"Content filtered"}}]}:{},usage:J}),{completion:_e,chunks:B}}}throw I!==1/0?new s(429,"all_routes_exhausted","all eligible model routes are rate-limited",void 0,{retry_after_s:Math.max(1,Math.ceil((I-Date.now())/1e3)),served_model:b?`${b.provider}/${b.model}`:z,...b?{rate_limit:{provider:b.provider,model:b.model,...b.account?{account:b.account}:{},scope:de(b.observation.scope),...b.observation.claim?{claim:b.observation.claim}:{},headers:b.observation.headers}}:{}},void 0,!0):e.model_policy==="exact"?new s(400,"substitution_refused","the requested model is unavailable"):new s(404,"model_unavailable","no usable model route is available")}u(it,"runLlm");export{st as llmCallerOf,rt as parseLlmRequest,it as runLlm};
@@ -59,6 +59,8 @@ export interface UnregisteredConcreteModel {
59
59
  candidate: RoutedCandidate;
60
60
  reason: 'unregistered';
61
61
  }
62
+ /** The route family of an app-paid node's recipe: the intent is `app/<strength>`. */
63
+ export declare const APP_FAMILY = "app";
62
64
  export declare function parseModelSpec(spec: string): {
63
65
  providerId: string;
64
66
  modelId: string;
@@ -1 +1 @@
1
- var E=Object.defineProperty;var u=(e,t)=>E(e,"name",{value:t,configurable:!0});import{readMergedLaunchConfig as P}from"./config.js";import{isManagedProvider as w,readRotationConfig as U,coolingUntilFor as M,readSubscriptionPool as D,resolveProviderCandidates as G}from"./subscription-state.js";const b=["ultra","strong","medium","light"],L=new Set(["off","minimal","low","medium","high","xhigh","max"]);function x(e){const t=e.indexOf("/");if(t<=0||t===e.length-1)return;const o=e.slice(0,t),n=e.slice(t+1),s=n.lastIndexOf(":"),f=s>0?n.slice(s+1).toLowerCase():void 0;return{providerId:o,modelId:f!==void 0&&L.has(f)?n.slice(0,s):n,...f!==void 0&&L.has(f)&&f!=="off"?{thinkingLevel:f}:{}}}u(x,"parseModelSpec");function H(e){const t=u(o=>{const n=e[o].strong.slice(0,e[o].strong.indexOf("/"));return{family:o,credentialSource:"default",providerId:n,models:e[o]}},"route");return{anthropic:t("anthropic"),openai:t("openai")}}u(H,"synthesizeRoutes");function $(e,t){return[e,...t.filter(o=>o!==e)]}u($,"ordered");function K(e,t){return $(e,t).filter((o,n,s)=>s.indexOf(o)===n)}u(K,"strengths");function j(e,t,o){if(e.kind==="exact"){const r=x(e.spec);return r?[{routeId:"exact",credentialSource:"exact",family:r.providerId,strength:"strong",...r,rank:0}]:[]}const n=P(t,o??null),s={...n.modelRoutes??{},...n.legacyModelRoutes??{}},f=Object.keys(s).length>0?s:H(n.modelLadders),l=Object.entries(f),m=n.modelRouting?.strengthFallback??[U().defaultFallbackStrength??"strong"],S=l.map(([,r])=>r.family).filter((r,d,a)=>a.indexOf(r)===d),I=l.map(([,r])=>r.credentialSource).filter((r,d,a)=>a.indexOf(r)===d),c=e.kind==="concrete"?x(e.spec):void 0,v=e.kind==="logical"?e:void 0,i=u((r,d)=>{const a=x(r);return a!==void 0&&c!==void 0&&a.providerId===c.providerId&&a.modelId===c.modelId&&(!d||a.thinkingLevel===c.thinkingLevel)},"matchesConcrete"),p=c===void 0?void 0:l.find(([,r])=>Object.values(r.models).some(d=>i(d,!1))),y=v?.strength??(()=>{if(!c)return"strong";for(const r of[!0,!1])for(const d of r?b:[...b].reverse())if(l.some(([,a])=>{const h=a.models[d];return h!==void 0&&i(h,r)}))return d;return"strong"})(),O=v?.family??p?.[1].family??c?.providerId??"anthropic",T=c?I:n.modelRouting?.credentialSourceOrder??I,N=c?[O]:n.modelRouting?.familyOrder??S,R=[],F=new Set,C=u((r,d,a,h)=>{const g=x(h);if(!g||c&&g.modelId!==c.modelId)return;const k=JSON.stringify([r,g.providerId,g.modelId,g.thinkingLevel]);F.has(k)||(F.add(k),R.push({routeId:r,credentialSource:d.credentialSource,family:d.family,strength:a,...g,rank:R.length}))},"append");if(c){const[r,d]=p??["concrete",{family:O,credentialSource:"default"}];C(r,d,y,e.spec)}for(const r of T)for(const d of c?[y]:K(y,m))for(const a of $(O,N))for(const[h,g]of l){if(g.credentialSource!==r||g.family!==a)continue;const k=g.models[d];k&&C(h,g,d,k)}return R.map((r,d)=>({...r,rank:d}))}u(j,"expandModelCandidates");function X(e,t,o={}){if(e===void 0||e.kind==="logical")return;const n=j(e,o.cwd,o.profileId)[0];if(!(n===void 0||t.find(n.providerId,n.modelId)!==void 0))return{request:e,candidate:n,reason:"unregistered"}}u(X,"unregisteredConcreteModel");function A(e,t,o=Date.now()){const n=t.find(e.providerId,e.modelId);if(n===void 0)return{registered:!1,authenticated:!1};if(!w(e.providerId))return{registered:!0,authenticated:t.hasConfiguredAuth(n)};const s=D(e.providerId).filter(l=>l.authFailure===void 0),f=s.length>0&&s.every(l=>M(l,e.modelId)>o);return{registered:!0,authenticated:s.length>0||t.hasConfiguredAuth(n),...f?{coolingUntil:Math.min(...s.map(l=>M(l,e.modelId)))}:{}}}u(A,"probeRouteAvailability");function _(e,t,o={}){const n=j(e,o.cwd,o.profileId),s=new Set(n.map(i=>`${i.providerId}/${i.modelId}`)),f=e.kind==="logical"?t.getAll().filter(i=>!s.has(`${i.provider}/${i.id}`)).map((i,p)=>({routeId:`registry:${i.provider}/${i.id}`,credentialSource:"registry",family:i.provider,strength:"strong",providerId:i.provider,modelId:i.id,rank:n.length+p})):[],l=[...n,...f],m=o.now??Date.now(),S=l.some(i=>{const p=A(i,t,m);return w(i.providerId)&&p.registered&&p.authenticated}),I=G(l.map(i=>({...i,...A(i,t,m),automaticFallback:i.routeId.startsWith("registry:")?w(i.providerId)||!S:!0})),m,{pinnedToFirst:e.kind==="exact"}),c=I.candidates[0],v=c===void 0?void 0:t.find(c.providerId,c.modelId);return{...c!==void 0&&v!==void 0?{selected:{model:v,candidate:c}}:{},rejected:I.rejected}}u(_,"selectInitialModelRoute");function B(e,t,o){if(e){if(o)return{kind:"exact",spec:e};if(t){const n=t.lastIndexOf("/"),s=n>0?t.slice(n+1):"";if(b.includes(s))return{kind:"logical",family:t.slice(0,n),strength:s,spec:e}}return{kind:"concrete",spec:e}}}u(B,"modelRequestFromConfig");export{j as expandModelCandidates,B as modelRequestFromConfig,x as parseModelSpec,A as probeRouteAvailability,_ as selectInitialModelRoute,X as unregisteredConcreteModel};
1
+ var E=Object.defineProperty;var f=(e,t)=>E(e,"name",{value:t,configurable:!0});import{readMergedLaunchConfig as U}from"./config.js";import{isManagedProvider as F,readRotationConfig as D,coolingUntilFor as L,readSubscriptionPool as G,resolveProviderCandidates as H}from"./subscription-state.js";const S="app",b=["ultra","strong","medium","light"],A=new Set(["off","minimal","low","medium","high","xhigh","max"]);function x(e){const t=e.indexOf("/");if(t<=0||t===e.length-1)return;const o=e.slice(0,t),n=e.slice(t+1),s=n.lastIndexOf(":"),u=s>0?n.slice(s+1).toLowerCase():void 0;return{providerId:o,modelId:u!==void 0&&A.has(u)?n.slice(0,s):n,...u!==void 0&&A.has(u)&&u!=="off"?{thinkingLevel:u}:{}}}f(x,"parseModelSpec");function K(e){const t=f(o=>{const n=e[o].strong.slice(0,e[o].strong.indexOf("/"));return{family:o,credentialSource:"default",providerId:n,models:e[o]}},"route");return{anthropic:t("anthropic"),openai:t("openai")}}f(K,"synthesizeRoutes");function $(e,t){return[e,...t.filter(o=>o!==e)]}f($,"ordered");function _(e,t){return $(e,t).filter((o,n,s)=>s.indexOf(o)===n)}f(_,"strengths");function j(e,t,o){if(e.kind==="exact"){const r=x(e.spec);return r?[{routeId:"exact",credentialSource:"exact",family:r.providerId,strength:"strong",...r,rank:0}]:[]}if(e.kind==="logical"&&e.family===S){const r=x(e.spec);return r?[{routeId:S,credentialSource:S,family:S,strength:e.strength,...r,rank:0}]:[]}const n=U(t,o??null),s={...n.modelRoutes??{},...n.legacyModelRoutes??{}},u=Object.keys(s).length>0?s:K(n.modelLadders),l=Object.entries(u),m=n.modelRouting?.strengthFallback??[D().defaultFallbackStrength??"strong"],y=l.map(([,r])=>r.family).filter((r,d,a)=>a.indexOf(r)===d),I=l.map(([,r])=>r.credentialSource).filter((r,d,a)=>a.indexOf(r)===d),c=e.kind==="concrete"?x(e.spec):void 0,v=e.kind==="logical"?e:void 0,i=f((r,d)=>{const a=x(r);return a!==void 0&&c!==void 0&&a.providerId===c.providerId&&a.modelId===c.modelId&&(!d||a.thinkingLevel===c.thinkingLevel)},"matchesConcrete"),p=c===void 0?void 0:l.find(([,r])=>Object.values(r.models).some(d=>i(d,!1))),O=v?.strength??(()=>{if(!c)return"strong";for(const r of[!0,!1])for(const d of r?b:[...b].reverse())if(l.some(([,a])=>{const h=a.models[d];return h!==void 0&&i(h,r)}))return d;return"strong"})(),R=v?.family??p?.[1].family??c?.providerId??"anthropic",T=c?I:n.modelRouting?.credentialSourceOrder??I,N=c?[R]:n.modelRouting?.familyOrder??y,w=[],C=new Set,M=f((r,d,a,h)=>{const g=x(h);if(!g||c&&g.modelId!==c.modelId)return;const k=JSON.stringify([r,g.providerId,g.modelId,g.thinkingLevel]);C.has(k)||(C.add(k),w.push({routeId:r,credentialSource:d.credentialSource,family:d.family,strength:a,...g,rank:w.length}))},"append");if(c){const[r,d]=p??["concrete",{family:R,credentialSource:"default"}];M(r,d,O,e.spec)}for(const r of T)for(const d of c?[O]:_(O,m))for(const a of $(R,N))for(const[h,g]of l){if(g.credentialSource!==r||g.family!==a)continue;const k=g.models[d];k&&M(h,g,d,k)}return w.map((r,d)=>({...r,rank:d}))}f(j,"expandModelCandidates");function X(e,t,o={}){if(e===void 0||e.kind==="logical")return;const n=j(e,o.cwd,o.profileId)[0];if(!(n===void 0||t.find(n.providerId,n.modelId)!==void 0))return{request:e,candidate:n,reason:"unregistered"}}f(X,"unregisteredConcreteModel");function P(e,t,o=Date.now()){const n=t.find(e.providerId,e.modelId);if(n===void 0)return{registered:!1,authenticated:!1};if(!F(e.providerId))return{registered:!0,authenticated:t.hasConfiguredAuth(n)};const s=G(e.providerId).filter(l=>l.authFailure===void 0),u=s.length>0&&s.every(l=>L(l,e.modelId)>o);return{registered:!0,authenticated:s.length>0||t.hasConfiguredAuth(n),...u?{coolingUntil:Math.min(...s.map(l=>L(l,e.modelId)))}:{}}}f(P,"probeRouteAvailability");function Y(e,t,o={}){const n=j(e,o.cwd,o.profileId),s=new Set(n.map(i=>`${i.providerId}/${i.modelId}`)),u=e.kind==="logical"?t.getAll().filter(i=>!s.has(`${i.provider}/${i.id}`)).map((i,p)=>({routeId:`registry:${i.provider}/${i.id}`,credentialSource:"registry",family:i.provider,strength:"strong",providerId:i.provider,modelId:i.id,rank:n.length+p})):[],l=[...n,...u],m=o.now??Date.now(),y=l.some(i=>{const p=P(i,t,m);return F(i.providerId)&&p.registered&&p.authenticated}),I=H(l.map(i=>({...i,...P(i,t,m),automaticFallback:i.routeId.startsWith("registry:")?F(i.providerId)||!y:!0})),m,{pinnedToFirst:e.kind==="exact"}),c=I.candidates[0],v=c===void 0?void 0:t.find(c.providerId,c.modelId);return{...c!==void 0&&v!==void 0?{selected:{model:v,candidate:c}}:{},rejected:I.rejected}}f(Y,"selectInitialModelRoute");function B(e,t,o){if(e){if(o)return{kind:"exact",spec:e};if(t){const n=t.lastIndexOf("/"),s=n>0?t.slice(n+1):"";if(b.includes(s))return{kind:"logical",family:t.slice(0,n),strength:s,spec:e}}return{kind:"concrete",spec:e}}}f(B,"modelRequestFromConfig");export{S as APP_FAMILY,j as expandModelCandidates,B as modelRequestFromConfig,x as parseModelSpec,P as probeRouteAvailability,Y as selectInitialModelRoute,X as unregisteredConcreteModel};
@@ -6,13 +6,17 @@ export declare const MAX_EVENTS_PER_CALL = 100;
6
6
  export interface IngestResult {
7
7
  /** Keys applied (or already applied): the sender marks them done. */
8
8
  accepted: string[];
9
- /** Keys this machine will never apply (unknown event, an id that is another machine's): the sender gives up on them. */
9
+ /** Keys this machine did not apply. The sender gives up on them, except a code `isCatalogLag` names, which it retries. */
10
10
  rejected: Array<{
11
11
  key: string;
12
12
  code: string;
13
13
  message: string;
14
14
  }>;
15
15
  }
16
+ /** Whether a rejection is only this machine's plugin catalog lagging the sender's: the event or kind the sender's newer catalog declares
17
+ * is not yet declared here (`eventSpec`'s `unknown_event`, `kindOf`'s `unknown_kind`), and the next bundle sync fixes it. Every other
18
+ * code the ingest can raise (`conflict`, `invalid_level`, `invalid_event`, `hop_limit`) is a defect of the event itself and never changes by waiting. */
19
+ export declare const isCatalogLag: (code: string) => boolean;
16
20
  /** The delivery key prefix of one sender's message; a delivery's full key adds `:<destination>`. */
17
21
  export declare const ingestKey: (via: string, messageId: string) => string;
18
22
  /** Apply a batch from machine `via`. One event's failure rejects only that event. Runs in the caller's write transaction.
@@ -1 +1 @@
1
- var g=Object.defineProperty;var c=(e,t)=>g(e,"name",{value:t,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{CrtrError as k}from"../errors.js";import{getById as S,getByRowid as O}from"../graph/objects.js";import{publish as L}from"../graph/events.js";import{markLinkWatch as h,recordWatch as A}from"../graph/watches.js";import{ackRows as x,claimRows as T}from"./outbox-store.js";import{ensureStub as E,localOwnerActor as w,stubOf as f}from"./stubs.js";const K=100,y=c((e,t)=>`peer:${e}:${t}`,"ingestKey");function $(e,t){return d(e,"SELECT 1 FROM peer_ingested WHERE key = ?").get(t)!==void 0?!0:d(e,"SELECT 1 FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ? LIMIT 1").get(`${t}:`,`${t};`)!==void 0}c($,"applied");const m=c((e,t,n)=>{d(e,"INSERT OR IGNORE INTO peer_ingested_destinations (key, ingested_at) VALUES (?, ?)").run(`${t}:${n}`,Date.now())},"markDestination");function N(e,t,n){return d(e,"SELECT 1 FROM peer_ingested_destinations WHERE key = ?").get(`${t}:${n}`)!==void 0||d(e,"SELECT 1 FROM deliveries WHERE idempotency_key = ?").get(`${t}:${n}`)!==void 0}c(N,"destinationDone");function I(e,t,n){return w(e,t)??E(e,t.id,t.type,n,void 0,t)}c(I,"actorFor");function _(e,t,n,r,i){const s=n.actor===null?null:I(e,n.actor,t),u=n.body_text,o=n.body===void 0&&u===void 0?void 0:{...n.body??{},...u===void 0?{}:{body:u}};L(e,{event:n.event,object:r,actor:s,level:n.level,attrs:n.attrs,...o===void 0?{}:{messageBody:o},idempotencyKey:y(t,n.message_id),...i===void 0?{}:{onlyWatchers:i}})}c(_,"apply");function C(e,t){const n=`ev:${t.message_id}:`;if(!t.key.startsWith(n)||t.key.length===n.length)return null;const r=S(e,t.key.slice(n.length));return r!==null&&r.type==="node"&&r.deleted_at===null&&f(e,r)===null?r:null}c(C,"localDestination");function P(e,t,n){const r={accepted:[],rejected:[]};for(const i of n){const s=y(t,i.message_id),u=$(e,s);e.exec("SAVEPOINT peer_event");try{const o=C(e,i);if(u&&o===null){e.exec("RELEASE peer_event"),r.accepted.push(i.key);continue}const l=E(e,i.object.id,i.object.type,t,void 0,i.object),a=i.linked===!0&&o!==null&&l.owner!==null?O(e,l.owner):null,p=a!==null&&a.type==="app"?a:null;if(o!==null&&d(e,"SELECT 1 FROM watches WHERE watcher = ? AND object = ?").get(o.rowid,l.rowid)===void 0?A(e,o,l,"act",{patterns:[">"],...p===null?{}:{linkApp:p}}):o!==null&&p!==null&&h(e,o,l,p),u)N(e,s,o.id)||_(e,t,i,l,[o.id]);else{_(e,t,i,l),d(e,"INSERT OR IGNORE INTO peer_ingested (key, ingested_at) VALUES (?, ?)").run(s,Date.now());for(const R of d(e,"SELECT destination_id FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ?").all(`${s}:`,`${s};`))m(e,s,R.destination_id)}o!==null&&m(e,s,o.id),e.exec("RELEASE peer_event"),r.accepted.push(i.key)}catch(o){e.exec("ROLLBACK TO peer_event"),e.exec("RELEASE peer_event");const l=o instanceof k?o.code:"invalid_event";r.rejected.push({key:i.key,code:l,message:o instanceof Error?o.message:String(o)})}}return r}c(P,"ingestPeerEvents");const v=c((e,t,n)=>f(e,t)?.home_machine===n,"isStubOf"),G=c((e,t,n)=>T(e,t,n),"claimFor"),U=c((e,t,n)=>x(e,t,n),"ackFor");export{K as MAX_EVENTS_PER_CALL,U as ackFor,G as claimFor,y as ingestKey,P as ingestPeerEvents,v as isStubOf};
1
+ var k=Object.defineProperty;var r=(e,n)=>k(e,"name",{value:n,configurable:!0});import{prepared as d}from"../sqlite-statements.js";import{CrtrError as R}from"../errors.js";import{getById as S,getByRowid as L}from"../graph/objects.js";import{publish as O}from"../graph/events.js";import{markLinkWatch as h,recordWatch as w}from"../graph/watches.js";import{ackRows as x,claimRows as A}from"./outbox-store.js";import{ensureStub as E,localOwnerActor as T,stubOf as f}from"./stubs.js";const v=100,K=r(e=>e==="unknown_event"||e==="unknown_kind","isCatalogLag"),y=r((e,n)=>`peer:${e}:${n}`,"ingestKey");function $(e,n){return d(e,"SELECT 1 FROM peer_ingested WHERE key = ?").get(n)!==void 0?!0:d(e,"SELECT 1 FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ? LIMIT 1").get(`${n}:`,`${n};`)!==void 0}r($,"applied");const m=r((e,n,t)=>{d(e,"INSERT OR IGNORE INTO peer_ingested_destinations (key, ingested_at) VALUES (?, ?)").run(`${n}:${t}`,Date.now())},"markDestination");function N(e,n,t){return d(e,"SELECT 1 FROM peer_ingested_destinations WHERE key = ?").get(`${n}:${t}`)!==void 0||d(e,"SELECT 1 FROM deliveries WHERE idempotency_key = ?").get(`${n}:${t}`)!==void 0}r(N,"destinationDone");function C(e,n,t){return T(e,n)??E(e,n.id,n.type,t,void 0,n)}r(C,"actorFor");function _(e,n,t,c,i){const s=t.actor===null?null:C(e,t.actor,n),u=t.body_text,o=t.body===void 0&&u===void 0?void 0:{...t.body??{},...u===void 0?{}:{body:u}};O(e,{event:t.event,object:c,actor:s,level:t.level,attrs:t.attrs,...o===void 0?{}:{messageBody:o},idempotencyKey:y(n,t.message_id),...i===void 0?{}:{onlyWatchers:i}})}r(_,"apply");function I(e,n){const t=`ev:${n.message_id}:`;if(!n.key.startsWith(t)||n.key.length===t.length)return null;const c=S(e,n.key.slice(t.length));return c!==null&&c.type==="node"&&c.deleted_at===null&&f(e,c)===null?c:null}r(I,"localDestination");function P(e,n,t){const c={accepted:[],rejected:[]};for(const i of t){const s=y(n,i.message_id),u=$(e,s);e.exec("SAVEPOINT peer_event");try{const o=I(e,i);if(u&&o===null){e.exec("RELEASE peer_event"),c.accepted.push(i.key);continue}const l=E(e,i.object.id,i.object.type,n,void 0,i.object),a=i.linked===!0&&o!==null&&l.owner!==null?L(e,l.owner):null,p=a!==null&&a.type==="app"?a:null;if(o!==null&&d(e,"SELECT 1 FROM watches WHERE watcher = ? AND object = ?").get(o.rowid,l.rowid)===void 0?w(e,o,l,"act",{patterns:[">"],...p===null?{}:{linkApp:p}}):o!==null&&p!==null&&h(e,o,l,p),u)N(e,s,o.id)||_(e,n,i,l,[o.id]);else{_(e,n,i,l),d(e,"INSERT OR IGNORE INTO peer_ingested (key, ingested_at) VALUES (?, ?)").run(s,Date.now());for(const g of d(e,"SELECT destination_id FROM deliveries WHERE idempotency_key >= ? AND idempotency_key < ?").all(`${s}:`,`${s};`))m(e,s,g.destination_id)}o!==null&&m(e,s,o.id),e.exec("RELEASE peer_event"),c.accepted.push(i.key)}catch(o){e.exec("ROLLBACK TO peer_event"),e.exec("RELEASE peer_event");const l=o instanceof R?o.code:"invalid_event";c.rejected.push({key:i.key,code:l,message:o instanceof Error?o.message:String(o)})}}return c}r(P,"ingestPeerEvents");const G=r((e,n,t)=>f(e,n)?.home_machine===t,"isStubOf"),U=r((e,n,t)=>A(e,n,t),"claimFor"),X=r((e,n,t)=>x(e,n,t),"ackFor");export{v as MAX_EVENTS_PER_CALL,X as ackFor,U as claimFor,y as ingestKey,P as ingestPeerEvents,K as isCatalogLag,G as isStubOf};
@@ -36,6 +36,9 @@ export interface OutboxRow {
36
36
  export declare const CLAIM_LEASE_MS: number;
37
37
  /** A row not delivered within this long is given up on. */
38
38
  export declare const OUTBOX_MAX_AGE_MS: number;
39
+ /** A row the receiver refused only because its plugin catalog lags the sender's (`isCatalogLag`) is retried this long from creation,
40
+ * then given up on: the receiver syncs within minutes, so a longer refusal is a real mismatch. Matches the app side's 24 h. */
41
+ export declare const CATALOG_LAG_RETRY_MS: number;
39
42
  /** Subscribe to enqueues (the delivery lane pushes a relay peer's row at once). Returns the unsubscribe. */
40
43
  export declare function onPeerEnqueued(listener: (peer: string) => void): () => void;
41
44
  /** Write one row; a key already present adds nothing. Returns whether a row was added. Runs in the caller's transaction. */
@@ -68,5 +71,6 @@ export declare function ackRows(db: DatabaseSync, via: string, acks: ReadonlyArr
68
71
  }>): string[];
69
72
  /** Retire rows done long ago, so the table stays small. */
70
73
  export declare function pruneOutbox(db: DatabaseSync, now: number, keepMs?: number): void;
71
- /** Give up on rows older than `OUTBOX_MAX_AGE_MS` that were never delivered. Returns how many. */
74
+ /** Give up on rows older than `OUTBOX_MAX_AGE_MS` that were never delivered, and on rows whose claim lapsed unacked (the receiver
75
+ * refused them as catalog lag and left them to be claimed again) once they are older than `CATALOG_LAG_RETRY_MS`. Returns how many. */
72
76
  export declare function expireOld(db: DatabaseSync, now: number): number;
@@ -1,4 +1,5 @@
1
- var c=Object.defineProperty;var a=(e,t)=>c(e,"name",{value:t,configurable:!0});import{prepared as r}from"../sqlite-statements.js";import{randomBytes as _}from"node:crypto";const x=10*6e4,m=720*60*6e4,d=new Set;function l(e){return d.add(e),()=>{d.delete(e)}}a(l,"onPeerEnqueued");function A(e,t,n=Date.now()){const o=r(e,`INSERT INTO peer_outbox (peer, kind, payload_json, idempotency_key, state, attempts, next_at, created_at)
2
- VALUES (?, ?, ?, ?, 'pending', 0, ?, ?) ON CONFLICT (idempotency_key) DO NOTHING`).run(t.peer,t.kind,JSON.stringify(t.payload),t.key,n,n);return Number(o.changes)===0?!1:(setImmediate(()=>{for(const i of[...d])try{i(t.peer)}catch{}}),!0)}a(A,"enqueuePeerRow");function L(e,t,n=200){return r(e,"SELECT * FROM peer_outbox WHERE state = 'pending' AND next_at <= ? ORDER BY id LIMIT ?").all(t,n)}a(L,"dueRows");function T(e,t,n,o=50){return r(e,"SELECT * FROM peer_outbox WHERE state = 'pending' AND peer = ? AND (attempts = 0 OR next_at <= ?) ORDER BY id LIMIT ?").all(t,n,o)}a(T,"pendingFor");const y=a((e,t)=>{r(e,"UPDATE peer_outbox SET state = 'done', claimed_by = NULL, next_at = NULL WHERE id = ? AND state IN ('pending', 'waiting')").run(t)},"markDone"),g=a((e,t,n)=>{r(e,"UPDATE peer_outbox SET state = 'waiting', attempts = ?, next_at = NULL WHERE id = ? AND state = 'pending'").run(n,t)},"markWaiting"),O=a((e,t,n,o,i)=>{r(e,"UPDATE peer_outbox SET attempts = ?, next_at = ?, first_failed_at = ? WHERE id = ? AND state = 'pending'").run(n,o,i,t)},"markRetry"),S=a((e,t)=>{r(e,"UPDATE peer_outbox SET state = 'expired', next_at = NULL WHERE id = ? AND state IN ('pending', 'waiting')").run(t)},"markExpired");function b(e,t,n,o=Date.now()){const i=Math.max(1,Math.min(n,100)),E=r(e,`SELECT * FROM peer_outbox WHERE peer = ?
3
- AND (state IN ('pending', 'waiting') OR (state = 'claimed' AND claimed_at <= ?)) ORDER BY id LIMIT ?`).all(t,o-x,i),s=_(16).toString("hex"),u=r(e,"UPDATE peer_outbox SET state = 'claimed', claimed_by = ?, claimed_at = ?, next_at = NULL WHERE id = ?");return E.map(p=>(u.run(s,o,p.id),{key:p.idempotency_key,claim_token:s,payload:JSON.parse(p.payload_json)}))}a(b,"claimRows");function f(e,t,n){const o=r(e,`UPDATE peer_outbox SET state = 'done', claimed_by = NULL WHERE idempotency_key = ? AND peer = ?
4
- AND state = 'claimed' AND claimed_by = ?`);return n.filter(i=>Number(o.run(i.key,t,i.claim_token).changes)>0).map(i=>i.key)}a(f,"ackRows");function k(e,t,n=1440*6e4){r(e,"DELETE FROM peer_outbox WHERE state IN ('done', 'expired') AND created_at < ?").run(t-n)}a(k,"pruneOutbox");function I(e,t){return Number(r(e,"UPDATE peer_outbox SET state = 'expired', next_at = NULL WHERE state IN ('pending', 'waiting') AND created_at < ?").run(t-m).changes)}a(I,"expireOld");export{x as CLAIM_LEASE_MS,m as OUTBOX_MAX_AGE_MS,f as ackRows,b as claimRows,L as dueRows,A as enqueuePeerRow,I as expireOld,y as markDone,S as markExpired,O as markRetry,g as markWaiting,l as onPeerEnqueued,T as pendingFor,k as pruneOutbox};
1
+ var u=Object.defineProperty;var a=(t,e)=>u(t,"name",{value:e,configurable:!0});import{prepared as r}from"../sqlite-statements.js";import{randomBytes as x}from"node:crypto";const E=10*6e4,m=720*60*6e4,N=1440*6e4,d=new Set;function l(t){return d.add(t),()=>{d.delete(t)}}a(l,"onPeerEnqueued");function L(t,e,n=Date.now()){const o=r(t,`INSERT INTO peer_outbox (peer, kind, payload_json, idempotency_key, state, attempts, next_at, created_at)
2
+ VALUES (?, ?, ?, ?, 'pending', 0, ?, ?) ON CONFLICT (idempotency_key) DO NOTHING`).run(e.peer,e.kind,JSON.stringify(e.payload),e.key,n,n);return Number(o.changes)===0?!1:(setImmediate(()=>{for(const i of[...d])try{i(e.peer)}catch{}}),!0)}a(L,"enqueuePeerRow");function T(t,e,n=200){return r(t,"SELECT * FROM peer_outbox WHERE state = 'pending' AND next_at <= ? ORDER BY id LIMIT ?").all(e,n)}a(T,"dueRows");function y(t,e,n,o=50){return r(t,"SELECT * FROM peer_outbox WHERE state = 'pending' AND peer = ? AND (attempts = 0 OR next_at <= ?) ORDER BY id LIMIT ?").all(e,n,o)}a(y,"pendingFor");const O=a((t,e)=>{r(t,"UPDATE peer_outbox SET state = 'done', claimed_by = NULL, next_at = NULL WHERE id = ? AND state IN ('pending', 'waiting')").run(e)},"markDone"),g=a((t,e,n)=>{r(t,"UPDATE peer_outbox SET state = 'waiting', attempts = ?, next_at = NULL WHERE id = ? AND state = 'pending'").run(n,e)},"markWaiting"),S=a((t,e,n,o,i)=>{r(t,"UPDATE peer_outbox SET attempts = ?, next_at = ?, first_failed_at = ? WHERE id = ? AND state = 'pending'").run(n,o,i,e)},"markRetry"),b=a((t,e)=>{r(t,"UPDATE peer_outbox SET state = 'expired', next_at = NULL WHERE id = ? AND state IN ('pending', 'waiting')").run(e)},"markExpired");function f(t,e,n,o=Date.now()){const i=Math.max(1,Math.min(n,100)),_=r(t,`SELECT * FROM peer_outbox WHERE peer = ?
3
+ AND (state IN ('pending', 'waiting') OR (state = 'claimed' AND claimed_at <= ?)) ORDER BY id LIMIT ?`).all(e,o-E,i),s=x(16).toString("hex"),c=r(t,"UPDATE peer_outbox SET state = 'claimed', claimed_by = ?, claimed_at = ?, next_at = NULL WHERE id = ?");return _.map(p=>(c.run(s,o,p.id),{key:p.idempotency_key,claim_token:s,payload:JSON.parse(p.payload_json)}))}a(f,"claimRows");function k(t,e,n){const o=r(t,`UPDATE peer_outbox SET state = 'done', claimed_by = NULL WHERE idempotency_key = ? AND peer = ?
4
+ AND state = 'claimed' AND claimed_by = ?`);return n.filter(i=>Number(o.run(i.key,e,i.claim_token).changes)>0).map(i=>i.key)}a(k,"ackRows");function I(t,e,n=1440*6e4){r(t,"DELETE FROM peer_outbox WHERE state IN ('done', 'expired') AND created_at < ?").run(e-n)}a(I,"pruneOutbox");function U(t,e){return Number(r(t,`UPDATE peer_outbox SET state = 'expired', next_at = NULL
5
+ WHERE (state IN ('pending', 'waiting') AND created_at < ?) OR (state = 'claimed' AND claimed_at <= ? AND created_at < ?)`).run(e-m,e-E,e-N).changes)}a(U,"expireOld");export{N as CATALOG_LAG_RETRY_MS,E as CLAIM_LEASE_MS,m as OUTBOX_MAX_AGE_MS,k as ackRows,f as claimRows,T as dueRows,L as enqueuePeerRow,U as expireOld,O as markDone,b as markExpired,S as markRetry,g as markWaiting,l as onPeerEnqueued,y as pendingFor,I as pruneOutbox};
@@ -33,7 +33,7 @@ export declare function discardFile(key: string): void;
33
33
  /** Discard every cached token for (`sub`, `grantee`). Returns how many went. */
34
34
  export declare function discardFor(sub: string, grantee: string, nowMs?: number): number;
35
35
  /** The (`sub`, `grantee`) pairs whose grant a sync revoked, removed, dropped,
36
- * replaced, or whose resolved scope set it changed. */
36
+ * replaced, or whose resolved scope set or inference endpoint it changed. */
37
37
  export declare function changedGrantPairs(before: readonly GrantRecord[], after: readonly GrantRecord[]): Array<{
38
38
  sub: string;
39
39
  grantee: string;
@@ -1 +1 @@
1
- var j=Object.defineProperty;var o=(e,t)=>j(e,"name",{value:t,configurable:!0});import{createHash as A}from"node:crypto";import{mkdirSync as M,readdirSync as E,readFileSync as F,rmSync as d}from"node:fs";import{join as h}from"node:path";import{crtrHome as D}from"../canvas/paths.js";import{atomicWriteJson as P}from"../fs-utils.js";const G=30;function l(){return h(D(),"secrets","outbound-tokens")}o(l,"tokenCacheDir");function U(e){return JSON.stringify([e.audience,e.sub,e.grantee,e.initiating_app,[...new Set(e.scope)].sort()])}o(U,"cacheKey");function m(e){return h(l(),`${A("sha256").update(e).digest("hex")}.json`)}o(m,"fileFor");function g(e){try{const t=JSON.parse(e);if(!Array.isArray(t)||typeof t[1]!="string"||typeof t[2]!="string")return null;const r=(Array.isArray(t[4])?t[4]:[]).flatMap(s=>typeof s=="string"?[v(s)]:[]).filter(s=>s!==null);return{sub:t[1],grantee:t[2],providers:[...new Set(r)]}}catch{return null}}o(g,"partsOf");function B(e){const t=g(e);return t===null?null:p(t.sub,t.grantee)}o(B,"pairOf");function p(e,t){return JSON.stringify([e,t])}o(p,"pairKey");function y(e,t){return JSON.stringify([e,t])}o(y,"providerKey");function v(e){const t=e.indexOf(":");return t>0?e.slice(0,t):null}o(v,"providerOf");function x(e){try{const t=JSON.parse(F(e,"utf8"));if(typeof t.key!="string"||typeof t.access_token!="string"||typeof t.jti!="string"||typeof t.exp!="number")return null;const n=typeof t.payer=="string"&&t.payer?t.payer:g(t.key)?.sub??"";return n?{...t,payer:n}:null}catch{return null}}o(x,"readFile");function K(e,t){return t<(e.exp-G)*1e3}o(K,"usable");function L(e,t=Date.now()){const n=m(e),r=x(n);return!r||r.key!==e||!K(r,t)?(d(n,{force:!0}),null):{access_token:r.access_token,jti:r.jti,exp:r.exp,payer:r.payer}}o(L,"cachedToken");const f=new Map,S=new Map;function w(e){const t=g(e);return t===null?0:(f.get(p(t.sub,t.grantee))??0)+t.providers.reduce((n,r)=>n+(S.get(y(t.sub,r))??0),0)}o(w,"discardEpoch");function Q(e,t,n=w(e)){return w(e)!==n?!1:(M(l(),{recursive:!0,mode:448}),P(m(e),{key:e,...t},{mode:384}),!0)}o(Q,"storeToken");function V(e){d(m(e),{force:!0})}o(V,"discardFile");function b(e,t,n){let r;try{r=E(l())}catch{return 0}let s=0;for(const c of r){if(!c.endsWith(".json"))continue;const i=h(l(),c),a=x(i),u=a?g(a.key):null,N=u!==null&&(e.has(p(u.sub,u.grantee))||u.providers.some(_=>t.has(y(u.sub,_))));a&&N?(d(i,{force:!0}),s++):(!a||n>=a.exp*1e3)&&d(i,{force:!0})}return s}o(b,"sweep");function X(e,t,n=Date.now()){const r=p(e,t);return f.set(r,(f.get(r)??0)+1),b(new Set([r]),new Set,n)}o(X,"discardFor");function O(e){const t=new Map;for(const n of e){const r=n.resolved_scopes.map(({scope:c,mode:i})=>`${i} ${c}`).sort(),s=p(n.sub,n.grantee);t.set(s,[...t.get(s)??[],JSON.stringify([n.id,n.status,r])])}return new Map([...t].map(([n,r])=>[n,JSON.stringify(r.sort())]))}o(O,"grantStates");function T(e,t){const n=O(e),r=O(t),s=[];for(const[c,i]of n){if(r.get(c)===i)continue;const[a,u]=JSON.parse(c);s.push({sub:a,grantee:u})}return s}o(T,"changedGrantPairs");function k(e){const t=new Set;for(const n of e)if(n.status==="active")for(const{scope:r}of n.resolved_scopes){const s=v(r);s!==null&&t.add(y(n.sub,s))}return t}o(k,"resolvedProviders");function J(e){return new Set(e.filter(t=>t.status==="active"&&t.grantee.startsWith("app:")).map(t=>p(t.sub,t.grantee)))}o(J,"connectedApps");function W(e,t){const n=k(t),r=new Set([...k(e)].filter(c=>!n.has(c))),s=J(t);for(const c of J(e)){if(s.has(c))continue;const[i,a]=JSON.parse(c);r.add(y(i,a.slice(4)))}return[...r]}o(W,"droppedProviders");function Y(e,t,n=Date.now()){const r=T(e,t),s=new Set(r.map(({sub:i,grantee:a})=>p(i,a))),c=new Set(W(e,t));for(const i of s)f.set(i,(f.get(i)??0)+1);for(const i of c)S.set(i,(S.get(i)??0)+1);return{pairs:s.size,providers:c.size,tokens:b(s,c,n)}}o(Y,"discardChangedGrantTokens");export{G as REUSE_MARGIN_S,U as cacheKey,L as cachedToken,T as changedGrantPairs,Y as discardChangedGrantTokens,w as discardEpoch,V as discardFile,X as discardFor,W as droppedProviders,Q as storeToken,l as tokenCacheDir};
1
+ var j=Object.defineProperty;var o=(t,e)=>j(t,"name",{value:e,configurable:!0});import{createHash as A}from"node:crypto";import{mkdirSync as M,readdirSync as E,readFileSync as F,rmSync as d}from"node:fs";import{join as h}from"node:path";import{crtrHome as D}from"../canvas/paths.js";import{atomicWriteJson as P}from"../fs-utils.js";const G=30;function l(){return h(D(),"secrets","outbound-tokens")}o(l,"tokenCacheDir");function U(t){return JSON.stringify([t.audience,t.sub,t.grantee,t.initiating_app,[...new Set(t.scope)].sort()])}o(U,"cacheKey");function m(t){return h(l(),`${A("sha256").update(t).digest("hex")}.json`)}o(m,"fileFor");function g(t){try{const e=JSON.parse(t);if(!Array.isArray(e)||typeof e[1]!="string"||typeof e[2]!="string")return null;const r=(Array.isArray(e[4])?e[4]:[]).flatMap(s=>typeof s=="string"?[v(s)]:[]).filter(s=>s!==null);return{sub:e[1],grantee:e[2],providers:[...new Set(r)]}}catch{return null}}o(g,"partsOf");function B(t){const e=g(t);return e===null?null:p(e.sub,e.grantee)}o(B,"pairOf");function p(t,e){return JSON.stringify([t,e])}o(p,"pairKey");function y(t,e){return JSON.stringify([t,e])}o(y,"providerKey");function v(t){const e=t.indexOf(":");return e>0?t.slice(0,e):null}o(v,"providerOf");function x(t){try{const e=JSON.parse(F(t,"utf8"));if(typeof e.key!="string"||typeof e.access_token!="string"||typeof e.jti!="string"||typeof e.exp!="number")return null;const n=typeof e.payer=="string"&&e.payer?e.payer:g(e.key)?.sub??"";return n?{...e,payer:n}:null}catch{return null}}o(x,"readFile");function K(t,e){return e<(t.exp-G)*1e3}o(K,"usable");function L(t,e=Date.now()){const n=m(t),r=x(n);return!r||r.key!==t||!K(r,e)?(d(n,{force:!0}),null):{access_token:r.access_token,jti:r.jti,exp:r.exp,payer:r.payer}}o(L,"cachedToken");const f=new Map,S=new Map;function w(t){const e=g(t);return e===null?0:(f.get(p(e.sub,e.grantee))??0)+e.providers.reduce((n,r)=>n+(S.get(y(e.sub,r))??0),0)}o(w,"discardEpoch");function Q(t,e,n=w(t)){return w(t)!==n?!1:(M(l(),{recursive:!0,mode:448}),P(m(t),{key:t,...e},{mode:384}),!0)}o(Q,"storeToken");function V(t){d(m(t),{force:!0})}o(V,"discardFile");function b(t,e,n){let r;try{r=E(l())}catch{return 0}let s=0;for(const c of r){if(!c.endsWith(".json"))continue;const i=h(l(),c),a=x(i),u=a?g(a.key):null,N=u!==null&&(t.has(p(u.sub,u.grantee))||u.providers.some(_=>e.has(y(u.sub,_))));a&&N?(d(i,{force:!0}),s++):(!a||n>=a.exp*1e3)&&d(i,{force:!0})}return s}o(b,"sweep");function X(t,e,n=Date.now()){const r=p(t,e);return f.set(r,(f.get(r)??0)+1),b(new Set([r]),new Set,n)}o(X,"discardFor");function O(t){const e=new Map;for(const n of t){const r=n.resolved_scopes.map(({scope:c,mode:i})=>`${i} ${c}`).sort(),s=p(n.sub,n.grantee);e.set(s,[...e.get(s)??[],JSON.stringify([n.id,n.status,r,n.inference_endpoint??null])])}return new Map([...e].map(([n,r])=>[n,JSON.stringify(r.sort())]))}o(O,"grantStates");function T(t,e){const n=O(t),r=O(e),s=[];for(const[c,i]of n){if(r.get(c)===i)continue;const[a,u]=JSON.parse(c);s.push({sub:a,grantee:u})}return s}o(T,"changedGrantPairs");function k(t){const e=new Set;for(const n of t)if(n.status==="active")for(const{scope:r}of n.resolved_scopes){const s=v(r);s!==null&&e.add(y(n.sub,s))}return e}o(k,"resolvedProviders");function J(t){return new Set(t.filter(e=>e.status==="active"&&e.grantee.startsWith("app:")).map(e=>p(e.sub,e.grantee)))}o(J,"connectedApps");function W(t,e){const n=k(e),r=new Set([...k(t)].filter(c=>!n.has(c))),s=J(e);for(const c of J(t)){if(s.has(c))continue;const[i,a]=JSON.parse(c);r.add(y(i,a.slice(4)))}return[...r]}o(W,"droppedProviders");function Y(t,e,n=Date.now()){const r=T(t,e),s=new Set(r.map(({sub:i,grantee:a})=>p(i,a))),c=new Set(W(t,e));for(const i of s)f.set(i,(f.get(i)??0)+1);for(const i of c)S.set(i,(S.get(i)??0)+1);return{pairs:s.size,providers:c.size,tokens:b(s,c,n)}}o(Y,"discardChangedGrantTokens");export{G as REUSE_MARGIN_S,U as cacheKey,L as cachedToken,T as changedGrantPairs,Y as discardChangedGrantTokens,w as discardEpoch,V as discardFile,X as discardFor,W as droppedProviders,Q as storeToken,l as tokenCacheDir};