@north-light/cli 0.3.453 → 0.3.455
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/error-codes.d.ts +25 -0
- package/dist/api/error-codes.js +1 -1
- package/dist/builtin-memory/crouter-sdk/guides/apps-on-northlight.md +1 -1
- package/dist/builtin-memory/crouter-sdk/release-notes.md +6 -0
- package/dist/clients/attach/viewer.js +413 -413
- package/dist/commands/pkg/browse/catalog.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +1 -1
- package/dist/core/command-manifests/registry.d.ts +2 -1
- package/dist/core/command-plugins/bundle.d.ts +3 -10
- package/dist/core/command-plugins/bundle.js +1 -1
- package/dist/core/command-plugins/compose.js +1 -1
- package/dist/core/command-plugins/discovery.js +1 -1
- package/dist/core/command-plugins/endpoint.d.ts +12 -0
- package/dist/core/command-plugins/endpoint.js +1 -1
- package/dist/core/command.d.ts +13 -0
- package/dist/core/command.js +9 -9
- package/dist/core/peer/client.d.ts +4 -2
- package/dist/core/peer/client.js +1 -1
- package/dist/core/peer/directory.d.ts +7 -0
- package/dist/core/peer/directory.js +1 -1
- package/dist/core/peer/envelope.d.ts +4 -0
- package/dist/core/peer/envelope.js +1 -1
- package/dist/core/peer/home.js +1 -1
- package/dist/core/providers/bundles.d.ts +2 -0
- package/dist/core/providers/bundles.js +2 -2
- package/dist/core/providers/call.d.ts +2 -0
- package/dist/core/providers/call.js +1 -1
- package/dist/core/providers/device.d.ts +39 -0
- package/dist/core/providers/device.js +1 -0
- package/dist/core/providers/errors.js +1 -1
- package/dist/core/runtime/output-schema-refs.d.ts +3 -0
- package/dist/core/runtime/output-schema-refs.js +1 -0
- package/dist/core/runtime/structured-output.d.ts +16 -5
- package/dist/core/runtime/structured-output.js +8 -6
- package/dist/daemon/api/handlers/messages.js +2 -2
- package/dist/daemon/api/handlers/nodes.js +1 -1
- package/dist/daemon/api/handlers/objects.js +2 -2
- package/dist/daemon/api/handlers/providers.d.ts +2 -0
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/handlers/structured-requests.js +1 -1
- package/dist/daemon/peer/outbox.js +1 -1
- package/dist/daemon/peer/relay-client.js +1 -1
- package/dist/daemon/peer/relay-protocol.d.ts +1 -1
- package/dist/daemon/peer/relay-protocol.js +1 -1
- package/dist/types.d.ts +4 -0
- package/docs/ci-runner.md +50 -4
- package/docs/sdk/guides/apps-on-northlight.md +1 -1
- package/docs/sdk/release-notes.md +6 -0
- package/package.json +3 -3
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
package/dist/core/command.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var
|
|
1
|
+
var J=Object.defineProperty;var f=(e,i)=>J(e,"name",{value:i,configurable:!0});import{renderRoot as F,renderBranch as V,renderLeafArgv as O,withAsyncHelpState as z,collapseWideChildren as G,rootPromotedChildren as I}from"./help.js";import{beginPreview as L,publishPreviewResult as K,readStdinRaw as B,peekStdinRaw as D,emit as Y,handle as Q,setJsonOutput as X,isJsonOutput as Z}from"./io.js";import{renderResult as ee}from"./render.js";import{CrtrError as N}from"./errors.js";import{isManifestStaleError as ne}from"./manifest-stale.js";import{operationIdContext as te}from"./events/operation-id.js";import{ExitCode as S}from"../types.js";import{envNodeId as ie}from"../shared/env.js";import{readFileSync as re}from"node:fs";import{spawnSync as ae}from"node:child_process";import{constants as oe}from"node:os";import{isatty as se}from"node:tty";import{resolve as le}from"node:path";function W(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))W(i);return Object.freeze(e)}f(W,"freezeCommandMetadata");function Fe(e){const i=W(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>z(i,r,n)),slash:e.slash===void 0?void 0:W(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(Fe,"defineLeaf");function de(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(de,"visibleSubCount");function Ve(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=de(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:i.tier??"normal",kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,children:e.children}}f(Ve,"defineBranch");function Ie(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Ie,"collectSlashSpecs");function Be(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0).map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),l=I(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:l.length>0?l:void 0,otherSubcommandCount:a.length-l.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(Be,"defineRoot");function fe(e){if(e.kind==="root")return e.subtrees.filter(i=>i.tier!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=G(e.children.filter(n=>n.tier!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(fe,"childNames");function ce(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const l=i[a];if(l==="-h"||l==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(ce,"walk");function ue(e){return e.kind==="root"?F(e.help):e.kind==="branch"?V(e.help):O(e.help)}f(ue,"renderNode");async function M(e,i,r){if(r!==void 0&&e.kind==="leaf")return O(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?O(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?V(await e.effectiveHelp()):ue(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:l},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:x}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),b=await l(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:x});return n+b.map($=>`
|
|
2
2
|
|
|
3
|
-
<plugin-help plugin="${
|
|
4
|
-
${
|
|
5
|
-
</plugin-help>`).join("")}
|
|
6
|
-
`),process.exitCode
|
|
7
|
-
`),process.exitCode
|
|
8
|
-
`),process.exitCode
|
|
9
|
-
`),process.exitCode
|
|
10
|
-
`))}}catch(
|
|
3
|
+
<plugin-help plugin="${$.plugin}">
|
|
4
|
+
${$.text}
|
|
5
|
+
</plugin-help>`).join("")}f(M,"renderNodeWithAddenda");function he(e){return e.some(i=>i==="-h"||i==="--help")}f(he,"helpRequested");function me(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(me,"focusedHelpFlag");function pe(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(pe,"rawArgsAfterPath");function ge(e,i){const r=ae(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new N("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,S.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=oe.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(ge,"runPassthrough");function we(e,i,r){const n=fe(e),a=n.length>0?n.join(", "):"(none)",l=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new N("unknown_path",`unknown subcommand: ${r}`,S.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${l}\` for how to find the rest.`:`Valid children: ${a}. Run \`${l}\` for the full list.`})}f(we,"unknownPathError");const be="https://account.northlight.ai";function ve(e){const i=be,r=`open ${i}/account/machines, choose Create join token, then run \`crtr sys join --directory ${i} --token <token>\``;return new N("not_joined",`\`crtr ${e}\` isn't a command on this machine. If it comes from Northlight, this machine isn't joined to your account yet: ${r}.`,S.USAGE,{received:e,next:`${r[0].toUpperCase()}${r.slice(1)}.`})}f(ve,"notJoinedError");async function xe(){try{const{readConfig:e}=await import("./config.js");return!e("user").runtime?.issuer}catch{return!1}}f(xe,"runtimeUnjoined");function p(e,i,r,n,a){return new N(e,i,S.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(p,"parseArgvError");function k(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f(k,"flagNameToKey");async function ke(e,i,r){const n={},a=new Set,l=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),x=e.find(t=>t.kind==="context-file"),b=e.filter(t=>t.kind==="flag"),$=new Map(b.map(t=>[t.name,t])),y=b.some(t=>t.sensitive===!0);for(const t of b)t.type==="bool"?n[k(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[k(t.name)]=t.default);const g=[];let v=0,_=!1,w=!1,c=0;for(;c<i.length;){const t=i[c];if(t==="--"&&!w){w=!0,c++;continue}if(!w&&t.startsWith("--")){const h=t.indexOf("=");let d,P;if(h!==-1?(d=t.slice(2,h),P=t.slice(h+1)):d=t.slice(2),d==="context-file"){if(x===void 0)throw p("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const m=P!==void 0?P:i[++c];if(m===void 0)throw p("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let H;try{H=re(m,"utf8")}catch{throw p("invalid_type",`--context-file: cannot read file: ${m}`,m,"context-file","Provide a readable file path.")}let q;try{q=JSON.parse(H)}catch{throw p("invalid_type",`--context-file: file is not valid JSON: ${m}`,m,"context-file","Ensure the file contains a valid JSON object.")}const T=k(x.name);n[T]=q,l.set(T,le(m)),a.add(T),c++;continue}const E=$.get(d);if(E===void 0)throw p("unknown_flag",`unknown flag: --${d}`,`--${d}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const U=k(d);if(a.add(U),E.type==="bool"){if(P!==void 0)throw p("invalid_type",`boolean flag --${d} takes no value`,`--${d}=${P}`,d,`Use --${d} (presence = true) with no value.`);n[U]=!0,c++;continue}const C=P!==void 0?P:i[++c];if(C===void 0||P===void 0&&C.startsWith("--")){const m=E.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${d} -h\` and read the focused value contract before retrying.`:void 0;throw p("missing_parameter",`--${d} requires a value`,`--${d}`,d,m)}let R;if(E.type==="int"){const m=Number(C);if(!Number.isInteger(m))throw p("invalid_type",`--${d} must be an integer`,C,d,`Provide an integer value for --${d}.`);R=m}else if(E.type==="enum"){const m=E.choices;if(m!==void 0&&!m.includes(C))throw p("invalid_type",`--${d} must be one of: ${m.join(", ")}`,C,d,`Retry with one of: ${m.join(", ")}.`);R=C}else R=C;if(E.repeatable===!0){const m=n[U],H=Array.isArray(m)?m:[];H.push(R),n[U]=H}else n[U]=R;c++;continue}const u=s[v];if(u!==void 0){const h=k(u.name);if(u.repeatable===!0){const d=n[h]??[];d.push(t),n[h]=d}else n[h]=t,v++;g.push(t),_=!0,a.add(h),c++;continue}if(g.length>0){if(o!==void 0&&s.length===0){g.push(t),_=!0,c++;continue}throw o!==void 0?p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):p("bad_invocation",y?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,y?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw p("bad_invocation",y?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,y?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");g.push(t),_=!0,c++}let A=!1;if(o!==void 0){if(g.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?p("bad_invocation",t.message,void 0,t.field,t.next):p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(g.length>0&&s.length===0&&o.allowPositional!==!1){const t=g[0];if(t==="-"){const u=await B();(u.trim()!==""||!o.required)&&(n[k(o.name)]=u),A=!0}else{if((await D()).trim()!==""){const h=o.positionalStdinConflict?.(n);throw p("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,h?.field??o.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[k(o.name)]=t,A=!0}}else{const t=se(0),u=await B();(u.trim()!==""||!o.required)&&(n[k(o.name)]=u),A=!t||_}}A&&o!==void 0&&a.add(k(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const u=k(t.name);if(a.has(u))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[u]=t.kind==="positional"&&t.repeatable===!0?[h]:h,a.add(u))}const j=[];for(const t of e){const u=k(t.name),h=t.kind==="flag"&&t.type==="bool"&&!a.has(u);if(t.required&&(h||n[u]===void 0||n[u]===null)){const d=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";j.push({display:d,name:t.name})}}if(j.length>0){const t=j.map(d=>d.display).join(", "),u=r?.leafPath,h=u!==void 0&&u.length>0?`\`crtr ${u.join(" ")} -h\``:"this command with -h";throw p("missing_parameter",j.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,j.map(d=>d.name).join(", "),`Run ${h} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(l),n}f(ke,"parseArgv");function ye(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const l=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&l===void 0){i.push(a);continue}const s=l??e[n+1];if(s===void 0||s===""||l===void 0&&s.startsWith("-"))throw p("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");l===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f(ye,"takeMachineSelector");const Se=new Set(["--json","--no-autostart"]);async function Me(e,i){const r=i.slice(2);r.includes("--json")&&X(!0);const n=r.filter(o=>!Se.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=ie()?{...e.help,commands:await Promise.all(e.help.commands.map(async x=>{const b=e.subtrees.find(v=>v.name===x.name);if(b?.effectiveHelp===void 0)return x;const y=((await b.effectiveHelp()).listing??[]).filter(v=>v.tier!=="hidden"),g=I(y).map(v=>({path:`${b.name} ${v.name}`,desc:v.tier==="important"?v.description:void 0}));return{...x,subcommands:g.length?g:void 0,otherSubcommandCount:y.length-g.length}}))}:e.help;process.stdout.write(F(o)+`
|
|
6
|
+
`),process.exitCode=S.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:o}=await import("../commands/sys/shared.js");process.stdout.write(o()+`
|
|
7
|
+
`),process.exitCode=S.SUCCESS;return}let{node:a,path:l,remaining:s}=ce(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&ge(a.passthrough,pe(r,l)),a.kind==="branch"&&a.unavailable!==void 0&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw new N("provider_unavailable",a.unavailable,S.NETWORK,{next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&await xe()?ve(s[0]):we(a,l,s[0]);if(he(s)){process.stdout.write(await M(a,l,me(a,s))+`
|
|
8
|
+
`),process.exitCode=S.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await M(a,l)+`
|
|
9
|
+
`),process.exitCode=S.SUCCESS;return}const o=a;L(l.join(" "));const x=o.help.params!==void 0?o.help.params:[];let b=new Set,$=new Map,y=s,g;if(o.machineSelector===!0&&!x.some(c=>c.kind==="flag"&&c.name==="machine")){const c=ye(s);y=c.rest,g=c.machine}const v=await ke(x,y,{leafPath:l,onProvidedParams:f(c=>{b=c},"onProvidedParams"),onContextFilePaths:f(c=>{$=c},"onContextFilePaths")}),_={providedParams:b,contextFilePaths:$,...g===void 0?{}:{machine:g}},w=await te.fresh(()=>o.run(v,_));if(K(w==null?w:o.preview?.(w)??w),w!=null)if(Z())Y(w);else{const c=o.render!==void 0?o.render(w):ee(w,o.help);process.stdout.write(c+(o.stdoutExact?.(w)?"":`
|
|
10
|
+
`))}}catch(o){if(ne(o))throw o;Q(o)}}f(Me,"runCli");export{be as DEFAULT_DIRECTORY_ISSUER,Se as GLOBAL_TOKENS,Ie as collectSlashSpecs,Ve as defineBranch,Fe as defineLeaf,Be as defineRoot,ve as notJoinedError,ke as parseArgv,ue as renderNode,Me as runCli,ye as takeMachineSelector,we as unknownPathError,ce as walk};
|
|
@@ -10,7 +10,7 @@ export declare const RUNTIME_STARTING_BACKOFF_MS: {
|
|
|
10
10
|
readonly first: 1000;
|
|
11
11
|
readonly max: 8000;
|
|
12
12
|
};
|
|
13
|
-
/** Largest response body read. */
|
|
13
|
+
/** Largest response body read, in bytes. Matches the relay's body cap. */
|
|
14
14
|
export declare const MAX_PEER_RESPONSE_BYTES: number;
|
|
15
15
|
export interface PeerRequest {
|
|
16
16
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
@@ -25,13 +25,15 @@ export interface PeerOptions {
|
|
|
25
25
|
envelope?: Envelope;
|
|
26
26
|
/** Ends the call early (the caller's own budget ran out); the result is then `{unreachable: timeout}`. */
|
|
27
27
|
signal?: AbortSignal;
|
|
28
|
+
/** Allow a `kind = device` machine as the target. Only device selection in `provider.call` passes it; every other caller treats a device machine as no peer at all. */
|
|
29
|
+
device?: boolean;
|
|
28
30
|
}
|
|
29
31
|
export interface PeerResponse {
|
|
30
32
|
status: number;
|
|
31
33
|
headers: Record<string, string>;
|
|
32
34
|
body: unknown;
|
|
33
35
|
}
|
|
34
|
-
export type UnreachableReason = 'no_directory' | 'unknown_machine' | 'directory_unreachable' | 'network' | 'timeout' | 'machine_offline' | 'runtime_starting';
|
|
36
|
+
export type UnreachableReason = 'no_directory' | 'unknown_machine' | 'directory_unreachable' | 'network' | 'timeout' | 'machine_offline' | 'runtime_starting' | 'not_a_crtr_peer';
|
|
35
37
|
export interface PeerUnreachable {
|
|
36
38
|
reason: UnreachableReason;
|
|
37
39
|
message: string;
|
package/dist/core/peer/client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var M=Object.defineProperty;var c=(e,r)=>M(e,"name",{value:r,configurable:!0});import{crtrVersion as S}from"../../shared/crtr-version.js";import{directory as $,directoryIssuer as v,normalizeMachineId as T}from"./directory.js";import{ENVELOPE_HEADER as N,encodeEnvelope as O}from"./envelope.js";import{peerTokens as x}from"./token.js";import{PEER_VERSION_FLOOR as d,PEER_VERSION_HEADER as A,peerVersionRefusal as k}from"./version.js";const P=12e4,U=6e4,w={first:1e3,max:8e3},b=16*1024*1024,H=c(e=>"unreachable"in e,"isUnreachable"),i=c((e,r)=>({unreachable:{reason:e,message:r}}),"unreachable");function B(e){const r=e?.error;if(typeof r=="string")return r;const s=r?.code;return typeof s=="string"?s:null}c(B,"errorCode");async function I(e){if(Number(e.headers.get("content-length")??"0")>b)throw new RangeError("the peer answer is too large");const s=new Uint8Array(await e.arrayBuffer());if(s.byteLength>b)throw new RangeError("the peer answer is too large");const o=new TextDecoder().decode(s);if(o==="")return null;try{return JSON.parse(o)}catch{return o}}c(I,"readBody");function L(e,r){const s=new URL(`${e.url}${r.path.startsWith("/")?"":"/"}${r.path}`),o=r.query instanceof URLSearchParams?r.query:Object.entries(r.query??{});for(const[u,l]of o instanceof URLSearchParams?o.entries():o)l!==void 0&&s.searchParams.append(u,String(l));return s.toString()}c(L,"targetUrl");function C(e){return c(async function(s,o,u={}){const l=e.issuer();if(l===null)return i("no_directory","this machine is not joined to a Northlight account, so it has no peers");let t;try{t=await e.directory.reach(s)}catch(a){return i("directory_unreachable",a instanceof Error?a.message:String(a))}if(t===null)return i("unknown_machine",`No machine ${T(s)} on your account.`);if(t.kind==="device"&&u.device!==!0)return i("not_a_crtr_peer",`${t.name} is a device, not a crtr peer.`);const h=t.crtr_version===null?null:k(t.crtr_version,d,e.version());if(h!==null)return{status:426,headers:{},body:{error:{code:h.code,message:`${t.name} runs crtr ${t.crtr_version}; peers below ${d} cannot take part. Update it with \`crtr sys update\`.`,details:{peer_version:t.crtr_version,floor:d,receiver_version:e.version()}}}};const R=L(t,o),_=e.now();let m=w.first,E=!1,g=!1;for(;;){let a;try{const n=await e.tokens.get(l,t.url);a=await e.fetch(R,{method:o.method,redirect:"manual",headers:{authorization:`Bearer ${n}`,[A]:e.version(),...u.envelope?{[N]:O(u.envelope)}:{},...o.body===void 0?{}:{"content-type":"application/json"}},...o.body===void 0?{}:{body:JSON.stringify(o.body)},signal:AbortSignal.any([AbortSignal.timeout(g?Math.max(1e3,Math.min(e.timeoutMs,e.startingBudgetMs-(e.now()-_))):e.timeoutMs),...u.signal?[u.signal]:[]])})}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?i("timeout",`${t.name} did not answer within ${Math.round(e.timeoutMs/1e3)} s.`):n instanceof Error&&/directory/.test(n.message)?i("directory_unreachable",n.message):i("network",`${t.name} could not be reached: ${n instanceof Error?n.message:String(n)}`)}let f;try{f=await I(a)}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?i("timeout",`${t.name} did not finish its answer within ${Math.round(e.timeoutMs/1e3)} s.`):i("network",`${t.name}'s answer could not be read: ${n instanceof Error?n.message:String(n)}`)}const y=B(f),p=Object.fromEntries(a.headers.entries());if(a.status===401&&!E){E=!0,e.tokens.drop(l,t.url);continue}if(a.status===503&&y==="machine_offline")return i("machine_offline",`${t.name} is offline.`);if(a.status===504)return i("timeout",`${t.name} did not answer in time.`);if(a.status===503&&y==="runtime_starting"){if(e.now()-_+m>e.startingBudgetMs)return i("runtime_starting",`${t.name} is still starting.`);await e.sleep(m),m=Math.min(m*2,w.max),g=!0;continue}return{status:a.status,headers:p,body:f}}},"peerCall")}c(C,"createPeerCall");const J=C({issuer:v,directory:$,tokens:x,fetch:c((e,r)=>fetch(e,r),"fetch"),sleep:c(e=>new Promise(r=>setTimeout(r,e)),"sleep"),now:Date.now,version:S,timeoutMs:P,startingBudgetMs:U});export{b as MAX_PEER_RESPONSE_BYTES,P as PEER_CALL_TIMEOUT_MS,w as RUNTIME_STARTING_BACKOFF_MS,U as RUNTIME_STARTING_RETRY_BUDGET_MS,C as createPeerCall,H as isUnreachable,J as peerCall};
|
|
@@ -18,7 +18,12 @@ export interface Machine {
|
|
|
18
18
|
crtr_version: string | null;
|
|
19
19
|
last_seen: string | null;
|
|
20
20
|
is_default?: boolean;
|
|
21
|
+
/** `runtime` (a crtr peer) or `device` (a call target only, never a peer). A missing kind from an older directory is `runtime`. */
|
|
22
|
+
kind: MachineKind;
|
|
23
|
+
/** The plugins the machine reported; `[]` when none. */
|
|
24
|
+
plugins: string[];
|
|
21
25
|
}
|
|
26
|
+
export type MachineKind = 'runtime' | 'device';
|
|
22
27
|
export interface MachineStatus {
|
|
23
28
|
machine_id: string;
|
|
24
29
|
url: string;
|
|
@@ -39,6 +44,8 @@ export interface DirectoryDeps {
|
|
|
39
44
|
export interface DirectoryClient {
|
|
40
45
|
/** Every live machine of the account. Refreshes the reach cache. */
|
|
41
46
|
list(): Promise<Machine[]>;
|
|
47
|
+
/** The last `list()` answer, however old; null before the first one. For a caller that can use a stale list when the directory is down. */
|
|
48
|
+
lastList(): Machine[] | null;
|
|
42
49
|
/** One machine's reach, cached; null when the directory does not know it (not_found, revoked). */
|
|
43
50
|
reach(machineId: string): Promise<Machine | null>;
|
|
44
51
|
/** `PUT /v1/machines/<own id>/status`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var x=Object.defineProperty;var i=(t,e)=>x(t,"name",{value:e,configurable:!0});import{rememberMachineName as y}from"./names.js";import{prepared as _}from"../sqlite-statements.js";import{openDb as C,withCanvasWrite as M}from"../canvas/db.js";import{readConfig as S}from"../config.js";import{runtimeClient as g}from"../grants/sync.js";import{crtrVersion as I}from"../../shared/crtr-version.js";import{installedPluginIds as R}from"../directory/join.js";import{peerTokens as A}from"./token.js";const b=5*6e4,O=1e4;class c extends Error{static{i(this,"DirectoryError")}status;constructor(e,a){super(a),this.status=e,this.name="DirectoryError"}}const d=i(t=>t.startsWith("runtime:")?t:`runtime:${t}`,"normalizeMachineId");function E(t){if(!t||typeof t!="object")return null;const{kind:e,relay:a}=t;return e==="stable"?{kind:"stable"}:e==="relay"&&typeof a=="string"?{kind:"relay",relay:a}:null}i(E,"asReach");function T(t){const e=t,a=E(e?.reach);if(!e||typeof e.machine_id!="string"||typeof e.url!="string"||a===null)throw new c(502,"the directory answered a machine record with a missing machine_id, url or reach");return{machine_id:e.machine_id,name:typeof e.name=="string"?e.name:e.machine_id,url:e.url.replace(/\/$/,""),reach:a,crtr_version:typeof e.crtr_version=="string"?e.crtr_version:null,last_seen:typeof e.last_seen=="string"?e.last_seen:null,kind:e.kind==="device"?"device":"runtime",plugins:Array.isArray(e.plugins)?e.plugins.filter(u=>typeof u=="string"):[],...typeof e.is_default=="boolean"?{is_default:e.is_default}:{}}}i(T,"asMachine");function U(t,e){const a=new Map;let u=null;async function m(s,o,r){const n=t.issuer();if(n===null)throw new c(0,"this daemon has no directory");const f=n.replace(/\/$/,"");for(let w=0;;w++){let l;try{const h=await t.tokens.get(f);l=await t.fetch(`${f}${o}`,{method:s,headers:{authorization:`Bearer ${h}`,...r===void 0?{}:{"content-type":"application/json"}},...r===void 0?{}:{body:JSON.stringify(r)},signal:AbortSignal.timeout(O)})}catch(h){throw new c(0,`the directory could not be reached: ${h instanceof Error?h.message:String(h)}`)}if(l.status===401&&w===0){t.tokens.drop(f);continue}if(!l.ok)throw new c(l.status,`directory ${s} ${o} answered ${l.status}`);try{return await l.json()}catch{throw new c(502,`directory ${s} ${o} answered a body that is not JSON`)}}}return i(m,"call"),{async list(){const s=await m("GET","/v1/machines");if(!Array.isArray(s))throw new c(502,"the directory answered a machine list that is not an array");const o=s.map(T),r=t.now();for(const n of o)a.set(n.machine_id,{machine:n,at:r}),y(n.machine_id,n.name);return u={machines:o,at:r},o},lastList(){return u===null?null:u.machines},async reach(s){const o=d(s),r=a.get(o);if(r&&t.now()-r.at<b)return r.machine;try{const n=T(await m("GET",`/v1/machines/${encodeURIComponent(o)}/reach`));return a.set(o,{machine:n,at:t.now()}),y(n.machine_id,n.name),n}catch(n){if(n instanceof c&&n.status===404)return a.delete(o),null;if(r&&n instanceof c&&n.status===0)return r.machine;throw n}},async status(s){const o=e(),r=await m("PUT",`/v1/machines/${encodeURIComponent(o)}/status`,s);if(!r||typeof r.machine_id!="string"||typeof r.url!="string")throw new c(502,"the directory answered a status record with a missing machine_id or url");return{machine_id:r.machine_id,url:r.url.replace(/\/$/,""),reach:E(r.reach)}},invalidate(s){s===void 0?(a.clear(),u=null):a.delete(d(s))}}}i(U,"createDirectoryClient");function $(){return d(g().runtime)}i($,"ownMachineId");function k(){return S("user").runtime?.issuer??null}i(k,"directoryIssuer");const p=U({issuer:k,tokens:A,fetch:i((t,e)=>fetch(t,e),"fetch"),now:Date.now},$),J=i(()=>p.list(),"listMachines"),V=i(t=>p.reach(t),"machineReach"),v="machine_url";function Y(){return _(C(),"SELECT value FROM runtime_state WHERE key = ?").get(v)?.value??null}i(Y,"machineUrl");function D(t){M(e=>{_(e,"INSERT INTO runtime_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(v,t)})}i(D,"storeMachineUrl");async function B(t=p){if(k()===null)return null;try{g()}catch{return null}const e=await t.status({plugins:R(),crtr_version:I()});if(e.machine_id!==$())throw new c(502,"the directory answered a status for another machine");return D(e.url),e}i(B,"reportMachineStatus");export{O as DIRECTORY_CALL_TIMEOUT_MS,c as DirectoryError,b as REACH_TTL_MS,U as createDirectoryClient,p as directory,k as directoryIssuer,J as listMachines,V as machineReach,Y as machineUrl,d as normalizeMachineId,$ as ownMachineId,B as reportMachineStatus};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RequestPrincipal } from '../../daemon/api/router.js';
|
|
2
|
+
import type { ProviderCaller } from '../../daemon/api/handlers/providers.js';
|
|
2
3
|
import { type RunPlace } from '../runs/confinement.js';
|
|
3
4
|
export declare const ENVELOPE_HEADER = "Crtr-Envelope";
|
|
4
5
|
/** A header larger than this is refused unread. */
|
|
@@ -38,3 +39,6 @@ export declare function envelopeFor(principal: EnvelopeCaller, context?: Envelop
|
|
|
38
39
|
export declare function encodeEnvelope(envelope: Envelope): string;
|
|
39
40
|
/** Parse and validate a `Crtr-Envelope` header value. Throws `EnvelopeError` for anything that is not the documented shape. */
|
|
40
41
|
export declare function decodeEnvelope(header: string): Envelope;
|
|
42
|
+
/** The envelope for a `provider.call` made by `caller` to a device machine: the account owner (`sub`), this machine, and the calling app and node
|
|
43
|
+
* (a person has no inner `act`). The device shows it in its recent-activity list and checks nothing in it. */
|
|
44
|
+
export declare function envelopeForProviderCaller(caller: ProviderCaller, sub: string, context?: Pick<EnvelopeContext, 'via' | 'placeOf'>): Envelope;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var p=Object.defineProperty;var l=(e,o)=>p(e,"name",{value:o,configurable:!0});import{joinedOwner as b}from"../directory/join.js";import{runPlace as d}from"../runs/confinement.js";import{ownMachineId as f}from"./directory.js";const _="Crtr-Envelope",h=16*1024;class r extends Error{static{l(this,"EnvelopeError")}constructor(o){super(o),this.name="EnvelopeError"}}function g(e,o={}){const n=o.via??f(),s=e.class==="person"?"":e.sub,t=o.owner??(s.startsWith("user:")?s:b());if(t==null)throw new r("this machine has no account owner to make a peer call for");if(e.class==="person")return{sub:t,act:{sub:n}};if(e.class==="broker"){const a=(o.placeOf??(u=>d(u)))(e.node);return{sub:t,act:{sub:n,act:{sub:e.app,node:e.node,node_scopes:e.scopes===null?null:[...e.scopes],place:a}}}}return{sub:t,act:{sub:n,act:{sub:e.app,node:e.anchor,node_scopes:e.scopes===null?null:[...e.scopes],place:{run:e.origin_run??"",isolated:e.isolated}}}}}l(g,"envelopeFor");function y(e){return Buffer.from(JSON.stringify(e),"utf8").toString("base64url")}l(y,"encodeEnvelope");const c=l(e=>typeof e=="string"&&e!=="","isString"),i=l(e=>typeof e=="object"&&e!==null&&!Array.isArray(e),"isRecord");function S(e){if(e.length>h)throw new r("the envelope is too large");if(!/^[A-Za-z0-9_-]+$/.test(e))throw new r("the envelope is not base64url");let o;try{o=JSON.parse(Buffer.from(e,"base64url").toString("utf8"))}catch{throw new r("the envelope is not JSON")}if(!i(o)||!c(o.sub)||!o.sub.startsWith("user:"))throw new r("the envelope names no user");const n=o.act;if(!i(n)||!c(n.sub)||!n.sub.startsWith("runtime:"))throw new r("the envelope names no calling machine");const s=n.act;if(s===void 0)return{sub:o.sub,act:{sub:n.sub}};if(!i(s)||!c(s.sub)||!s.sub.startsWith("app:"))throw new r("the envelope names no calling app");const{node:t,node_scopes:a,place:u}=s;if(t!==null&&!c(t))throw new r("the envelope node is not a node id");if(a!==null&&!(Array.isArray(a)&&a.every(c)))throw new r("the envelope node_scopes is not a list of scopes");if(u!==null&&!(i(u)&&typeof u.run=="string"&&typeof u.isolated=="boolean"))throw new r("the envelope place is malformed");return{sub:o.sub,act:{sub:n.sub,act:{sub:s.sub,node:t,node_scopes:a,place:u===null?null:{run:u.run,isolated:u.isolated}}}}}l(S,"decodeEnvelope");function O(e,o,n={}){const s=n.via??f();if(e.class==="person"||e.grantee===null)return{sub:o,act:{sub:s}};const t=e.node_id===null?null:(n.placeOf??(a=>d(a)))(e.node_id);return{sub:o,act:{sub:s,act:{sub:e.grantee,node:e.node_id,node_scopes:e.nodeScopes===null?null:[...e.nodeScopes],place:t}}}}l(O,"envelopeForProviderCaller");export{_ as ENVELOPE_HEADER,r as EnvelopeError,h as MAX_ENVELOPE_BYTES,S as decodeEnvelope,y as encodeEnvelope,g as envelopeFor,O as envelopeForProviderCaller};
|
package/dist/core/peer/home.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
var E=Object.defineProperty;var a=(n,r)=>E(n,"name",{value:r,configurable:!0});import{prepared as l}from"../sqlite-statements.js";import{getById as y}from"../graph/objects.js";import{isUnreachable as b,peerCall as x}from"./client.js";import{directoryIssuer as O,listMachines as T,normalizeMachineId as w,ownMachineId as I}from"./directory.js";import{stubOf as L}from"./stubs.js";const m={call:a((n,r)=>x(n,r),"call"),machines:T,self:I,now:Date.now,joined:a(()=>O()!==null,"joined")};function S(n,r){return l(n,"SELECT machine_id FROM peer_locations WHERE id = ?").get(r)?.machine_id??null}a(S,"cachedLocation");function g(n,r,t,i=Date.now()){l(n,`INSERT INTO peer_locations (id, machine_id, learned_at) VALUES (?, ?, ?)
|
|
2
|
-
ON CONFLICT(id) DO UPDATE SET machine_id = excluded.machine_id, learned_at = excluded.learned_at`).run(r,t,i)}a(g,"rememberLocation");function D(n,r){l(n,"DELETE FROM peer_locations WHERE id = ?").run(r)}a(D,"dropLocation");async function p(n,r,t=m,i){if(!t.joined())return{where:"nowhere"};let c;try{c=await t.machines()}catch(e){return{where:"unknown",unreachable:[`directory (${e instanceof Error?e.message:String(e)})`]}}const s=t.self(),_=c.filter(e=>e.machine_id!==s&&e.machine_id!==i),d=await Promise.all(_.map(async e=>{const o=await t.call(e.machine_id,{method:"POST",path:"/v1/peer/locate",body:{id:r}});if(b(o))return{m:e,state:"unreachable"};const h=o.body;return o.status===200&&h?.answer==="here"?{m:e,state:"here",machine:e.machine_id}:o.status===200&&h?.answer==="moved"&&typeof h.machine=="string"?{m:e,state:"here",machine:w(h.machine)}:o.status===200&&h?.answer==="no"?{m:e,state:"no"}:{m:e,state:"unreachable"}})),u=d.find(e=>e.state==="here");if(u&&"machine"in u)return g(n,r,u.machine,t.now()),{where:"machine",machine:u.machine,source:"locate"};const f=d.filter(e=>e.state==="unreachable").map(e=>e.m.name||e.m.machine_id);return f.length>0?{where:"unknown",unreachable:f}:{where:"nowhere"}}a(p,"locate");async function
|
|
2
|
+
ON CONFLICT(id) DO UPDATE SET machine_id = excluded.machine_id, learned_at = excluded.learned_at`).run(r,t,i)}a(g,"rememberLocation");function D(n,r){l(n,"DELETE FROM peer_locations WHERE id = ?").run(r)}a(D,"dropLocation");async function p(n,r,t=m,i){if(!t.joined())return{where:"nowhere"};let c;try{c=await t.machines()}catch(e){return{where:"unknown",unreachable:[`directory (${e instanceof Error?e.message:String(e)})`]}}const s=t.self(),_=c.filter(e=>e.machine_id!==s&&e.machine_id!==i&&e.kind!=="device"),d=await Promise.all(_.map(async e=>{const o=await t.call(e.machine_id,{method:"POST",path:"/v1/peer/locate",body:{id:r}});if(b(o))return{m:e,state:"unreachable"};const h=o.body;return o.status===200&&h?.answer==="here"?{m:e,state:"here",machine:e.machine_id}:o.status===200&&h?.answer==="moved"&&typeof h.machine=="string"?{m:e,state:"here",machine:w(h.machine)}:o.status===200&&h?.answer==="no"?{m:e,state:"no"}:{m:e,state:"unreachable"}})),u=d.find(e=>e.state==="here");if(u&&"machine"in u)return g(n,r,u.machine,t.now()),{where:"machine",machine:u.machine,source:"locate"};const f=d.filter(e=>e.state==="unreachable").map(e=>e.m.name||e.m.machine_id);return f.length>0?{where:"unknown",unreachable:f}:{where:"nowhere"}}a(p,"locate");async function v(n,r,t={}){const i=y(n,r);if(i!==null){const s=L(n,i);return s===null?{where:"here"}:{where:"machine",machine:s.home_machine,source:"stub"}}if(!(t.deps??m).joined())return{where:"nowhere"};if(t.hint)return{where:"machine",machine:w(t.hint),source:"hint"};const c=S(n,r);return c!==null?{where:"machine",machine:c,source:"cache"}:p(n,r,t.deps)}a(v,"homeOf");async function F(n,r,t,i=m){return D(n,r),p(n,r,i,t)}a(F,"relocate");export{S as cachedLocation,m as defaultHomeDeps,D as dropLocation,v as homeOf,p as locate,F as relocate,g as rememberLocation};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var X=Object.defineProperty;var i=(e,n)=>X(e,"name",{value:n,configurable:!0});import{existsSync as h,readdirSync as H,readFileSync as y,rmSync as b,statSync as x}from"node:fs";import{join as u}from"node:path";import{validateCommandManifest as Y}from"../../api/command-manifest/manifest.js";import{crtrHome as O}from"../canvas/paths.js";import{withCanvasWrite as ee}from"../canvas/db.js";import{bundleVersion as ne,isInstalledGeneration as re,recordCheckedGeneration as te}from"../command-plugins/bundle.js";import{readConfig as U,readState as oe}from"../config.js";import{emitEvent as g}from"../events/emit.js";import{withExclusiveDirectoryLockAsync as ie}from"../exclusive-lock.js";import{atomicWriteText as M,ensureDir as I,pathExists as ae}from"../fs-utils.js";import{reconcilePackageDocuments as se}from"../graph/package-docs.js";import{readPluginManifest as W}from"../manifest.js";import{bundleSwapLockPath as de}from"../plugin-swap-lock.js";import{pluginsDir as ue,userScopeRoot as ce}from"../scope.js";import{localGrants as fe}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const T="[A-Za-z0-9_-]{1,64}",le=new RegExp(`^(${T}):(${T})$`),z=new RegExp(`^${T}$`),pe=3e4,me=3e4;function ve(e){const n=new Set;for(const r of e)if(r.status==="active")for(const{scope:t}of r.resolved_scopes){const o=le.exec(t);o&&o[1]!=="crtr"&&n.add(o[1])}return[...n].sort()}i(ve,"providerIdsFromGrants");function w(e){return u(ue("user"),e)}i(w,"pluginRoot");function F(){return u(O(),"providers")}i(F,"providersRoot");function G(e,n){return u(F(),e,`v${n}`)}i(G,"majorDir");function
|
|
2
|
-
`),b(u(e,"bundle.tar"),{force:!0}),b(u(e,"etag"),{force:!0})}i($e,"retire");function K(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}i(K,"retiredError");async function R(e,n,r){const t=G(e,n),o=u(t,"bundle.tar"),a=h(o),s=a&&r&&h(u(t,"etag"))?y(u(t,"etag"),"utf8"):void 0,f=await q(e,{version:n,...s!==void 0?{ifNoneMatch:s}:{}});if(f.status===304)return"unchanged";if(f.status===410)throw $e(t),g({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),K(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`);try{await J(e,n,f.raw)}catch(l){
|
|
1
|
+
var X=Object.defineProperty;var i=(e,n)=>X(e,"name",{value:n,configurable:!0});import{existsSync as h,readdirSync as H,readFileSync as y,rmSync as b,statSync as x}from"node:fs";import{join as u}from"node:path";import{validateCommandManifest as Y}from"../../api/command-manifest/manifest.js";import{crtrHome as O}from"../canvas/paths.js";import{withCanvasWrite as ee}from"../canvas/db.js";import{bundleVersion as ne,isInstalledGeneration as re,recordCheckedGeneration as te}from"../command-plugins/bundle.js";import{readConfig as U,readState as oe}from"../config.js";import{emitEvent as g}from"../events/emit.js";import{withExclusiveDirectoryLockAsync as ie}from"../exclusive-lock.js";import{atomicWriteText as M,ensureDir as I,pathExists as ae}from"../fs-utils.js";import{reconcilePackageDocuments as se}from"../graph/package-docs.js";import{readPluginManifest as W}from"../manifest.js";import{bundleSwapLockPath as de}from"../plugin-swap-lock.js";import{pluginsDir as ue,userScopeRoot as ce}from"../scope.js";import{localGrants as fe}from"../grants/mirror.js";import{ProviderError as c}from"./errors.js";const T="[A-Za-z0-9_-]{1,64}",le=new RegExp(`^(${T}):(${T})$`),z=new RegExp(`^${T}$`),pe=3e4,me=3e4;function ve(e){const n=new Set;for(const r of e)if(r.status==="active")for(const{scope:t}of r.resolved_scopes){const o=le.exec(t);o&&o[1]!=="crtr"&&n.add(o[1])}return[...n].sort()}i(ve,"providerIdsFromGrants");function w(e){return u(ue("user"),e)}i(w,"pluginRoot");function F(){return u(O(),"providers")}i(F,"providersRoot");function G(e,n){return u(F(),e,`v${n}`)}i(G,"majorDir");function k(e){if(!z.test(e))throw new c("not_found",`"${e}" is not a provider id`,{param:"provider"})}i(k,"assertId");function j(){const e=U("user").runtime?.issuer;if(!e)throw new c("not_found","provider bundles need a directory-connected runtime");return e.replace(/\/+$/,"")}i(j,"directoryBase");async function q(e,n){const r=`${j()}/providers/${encodeURIComponent(e)}/plugin${n.version!==void 0?`?version=${n.version}`:""}`;let t;try{t=await fetch(r,{headers:{Accept:"application/x-tar",...n.ifNoneMatch!==void 0?{"If-None-Match":n.ifNoneMatch}:{}},signal:AbortSignal.timeout(pe)})}catch(s){return{status:"failed",message:`could not reach ${r}: ${s instanceof Error?s.message:String(s)}`}}if(t.status===304)return n.ifNoneMatch===void 0?{status:"failed",message:`HTTP 304 from ${r} without a conditional request`}:{status:304};if(t.status===404||t.status===410)return await t.body?.cancel().catch(()=>{}),{status:t.status===404?404:410,message:`HTTP ${t.status} from ${r}`};if(t.status<200||t.status>=300)return await t.body?.cancel().catch(()=>{}),{status:"failed",message:`HTTP ${t.status} from ${r}`};let o;try{o=new Uint8Array(await t.arrayBuffer())}catch(s){return{status:"failed",message:`reading ${r} failed: ${s instanceof Error?s.message:String(s)}`}}const a=t.headers.get("etag");return{status:200,raw:o,...a!==null?{etag:a}:{}}}i(q,"fetchProviderBundle");const _=new WeakSet;class S extends Error{static{i(this,"BundleInvalid")}issues;constructor(n,r){super(n),this.issues=r}}function V(e,n,r){if(r.provider===void 0)return"the archive carries no provider block";if(r.provider.id!==e)return`the archive is provider "${r.provider.id}", not "${e}"`;if(n!==void 0&&r.provider.major!==n)return`the archive is major ${r.provider.major}, not ${n}`;const t=["hooks","kinds","page_components","memory_extensions"].filter(o=>r[o]!==void 0);if(t.length>0)return`a provider bundle may not carry ${t.join(", ")}`}i(V,"wrongBundle");function A(e,n,r){g({level:"error",event:"daemon.providers.bundle_invalid",fields:{provider:e,...n!==void 0?{major:n}:{},...r instanceof S?{issues:r.issues}:{}},error:r})}i(A,"logInvalid");function Z(e,n,r){g({level:"warn",event:"daemon.providers.bundle_fetch_failed",fields:{provider:e,...n!==void 0?{major:n}:{},message:r}})}i(Z,"logFailure");async function L(){return import("../../commands/pkg/plugin-manage.js")}i(L,"pluginManage");const P=new Map,E=i(e=>`${O()}\0${e}`,"installKey");function he(e){const n=e,r=n?.code??n?.cause?.code;return typeof r=="string"&&/^[A-Z][A-Z0-9_]{1,30}$/.test(r)?`${r} on provider install directory`:e instanceof c&&e.message.includes("HTTP 404")?"directory returned HTTP 404":e instanceof c&&e.message.includes("HTTP 410")?"directory returned HTTP 410":"provider bundle could not be installed"}i(he,"publicInstallCause");function ge(e){if(m(e)===void 0)return P.get(E(e))??{status:"installing"}}i(ge,"providerInstallState");function we(e){return new c("provider_unavailable",`provider "${e}" unavailable: ${ge(e)?.error??"bundle installation pending"}`)}i(we,"unavailableProviderError");function m(e){const n=W(w(e));if(!(n?.provider===void 0||n.transport?.kind!=="http"&&n.transport?.kind!=="machine")&&ae(u(w(e),"commands.json"))){try{if(x(u(w(e),"commands.json")).size===0)return}catch{return}return{provider:n.provider,transport:n.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:n.transport.endpoint}}}}i(m,"installedManifest");async function C(e,n,r=!1){const t=ce();m(e)===void 0&&P.set(E(e),{status:"installing"});try{return I(u(t,"tmp")),await ie(de(t,e),async()=>{const o=w(e),a=W(o);if(a!==null&&a.provider===void 0)throw new c("provider_unavailable",`plugin "${e}" is installed and is not the directory's provider bundle`);const s=m(e)!==void 0;if(s&&r)return"unchanged";const f=s&&n?oe("user").plugins[e]?.etag:void 0,d=await q(e,f!==void 0?{ifNoneMatch:f}:{});if(d.status===304)return"unchanged";if(d.status===404||d.status===410)throw new c("not_found",`the directory serves no provider "${e}" (${d.message})`,{param:"provider"});if(d.status==="failed")throw new c("provider_unavailable",`provider "${e}" bundle fetch failed: ${d.message}`);const l=ne(d.raw);if(s&&re(e,"user",o,l))return te("user",e,d.etag,{swapped:!1}),"unchanged";const{installPluginArchive:v}=await L();try{await v(e,d.raw,{scope:"user",scopeRootPath:t,root:o,version:l,etag:d.etag,enable:!s,bundle:{endpoint:`${j()}/providers/${encodeURIComponent(e)}/plugin`},transport:{kind:"http",endpoint:j()},accept:i(p=>V(e,void 0,p),"accept")})}catch(p){A(e,void 0,p);const N=new c("not_found",`provider "${e}" served an invalid bundle: ${p instanceof Error?p.message:String(p)}`,{param:"provider"});throw _.add(N),N}try{ee(p=>se(p))}catch(p){g({level:"error",event:"daemon.providers.docs_reconcile_failed",fields:{provider:e},error:p})}return g({level:"info",event:"daemon.providers.bundle_installed",fields:{provider:e,version:l,replaced:s}}),P.delete(E(e)),s?"updated":"installed"},{timeoutMs:me,timeoutError:i(()=>new c("provider_unavailable",`timed out waiting for another process replacing plugin "${e}"`),"timeoutError")})}catch(o){throw m(e)===void 0&&P.set(E(e),{status:"install_failed",error:he(o)}),o instanceof c?o:(g({level:"error",event:"daemon.providers.bundle_install_failed",fields:{provider:e},error:o}),we(e))}}i(C,"refreshInstalled");async function We(e){k(e),m(e)===void 0&&await C(e,!1,!0)}i(We,"ensureProviderBundle");async function ze(e,n){return k(e),n.version!==void 0&&m(e)?.provider.major!==n.version?R(e,n.version,n.conditional):C(e,n.conditional)}i(ze,"refreshProviderBundle");async function J(e,n,r){const{validatePluginArchive:t}=await L(),o=await t(r);if(o.bundle===void 0)throw new S(`provider "${e}" major ${n} bundle is invalid: ${o.issues.map(s=>s.message).join("; ")}`,o.issues);const a=V(e,n,o.bundle);if(a!==void 0)throw new S(`provider "${e}" major ${n} bundle is invalid: ${a}`,[]);if(o.bundle.transport===void 0)throw new S(`provider "${e}" major ${n} bundle declares no transport`,[]);return o.bundle}i(J,"validateArchive");function $e(e){I(e),M(u(e,"retired"),`${new Date().toISOString()}
|
|
2
|
+
`),b(u(e,"bundle.tar"),{force:!0}),b(u(e,"etag"),{force:!0})}i($e,"retire");function K(e,n){return new c("provider_version_retired",`provider "${e}" major ${n} is retired`,{param:"version"})}i(K,"retiredError");async function R(e,n,r){const t=G(e,n),o=u(t,"bundle.tar"),a=h(o),s=a&&r&&h(u(t,"etag"))?y(u(t,"etag"),"utf8"):void 0,f=await q(e,{version:n,...s!==void 0?{ifNoneMatch:s}:{}});if(f.status===304)return"unchanged";if(f.status===410)throw $e(t),g({level:"info",event:"daemon.providers.major_retired",fields:{provider:e,major:n}}),K(e,n);if(f.status===404)throw new c("not_found",`provider "${e}" never served major ${n}`,{param:"version"});if(f.status==="failed")throw new c("provider_unavailable",`provider "${e}" major ${n} bundle fetch failed: ${f.message}`);try{await J(e,n,f.raw)}catch(l){A(e,n,l);const v=new c(a?"provider_unavailable":"not_found",l instanceof Error?l.message:String(l),{param:"version"});throw _.add(v),v}I(t),b(u(t,"retired"),{force:!0});const d=a&&Buffer.from(f.raw).equals(y(o));return d||M(o,f.raw),f.etag!==void 0?M(u(t,"etag"),f.etag):b(u(t,"etag"),{force:!0}),d?"unchanged":a?"updated":"installed"}i(R,"refreshPinned");function ye(){const e=[],n=F();if(!h(n))return e;for(const r of H(n)){if(!z.test(r))continue;let t;try{t=H(u(n,r))}catch{continue}for(const o of t){const a=/^v([1-9][0-9]*)$/.exec(o);a&&h(u(n,r,o,"bundle.tar"))&&e.push([r,Number(a[1])])}}return e}i(ye,"cachedMajors");function be(e){const n=Y(e,{transport:"http",reservedCoreNames:new Set});if(n.manifest===void 0)throw new c("not_found","the provider bundle's commands.json no longer validates");return n.manifest}i(be,"readCommands");const B=new Map,Q=new Map;function _e(e,n){const r=x(n),t=`${r.ino}:${r.mtimeMs}:${r.size}`,o=Q.get(e);if(o?.stamp===t)return o.commands;const a=be(JSON.parse(y(n,"utf8")));return Q.set(e,{stamp:t,commands:a}),a}i(_e,"installedCommands");async function Fe(e,n){k(e);const r=m(e);if(n===void 0||r?.provider.major===n){if(r===void 0)throw new c("not_found",`provider "${e}" has no installed bundle`,{param:"provider"});const l=w(e),v=_e(e,u(l,"commands.json"));return{id:e,major:r.provider.major,provider:r.provider,commands:v,transport:r.transport,source:"installed",root:l}}if(!Number.isInteger(n)||n<1)throw new c("invalid_request","version must be an integer of at least 1",{param:"version"});const t=G(e,n);if(h(u(t,"retired")))throw K(e,n);const o=u(t,"bundle.tar");h(o)||await R(e,n,!1);const a=`${e}/${n}`,s=x(o),f=`${s.mtimeMs}:${s.size}`;let d=B.get(a)?.stamp===f?B.get(a).bundle:void 0;if(d===void 0){try{d=await J(e,n,new Uint8Array(y(o)))}catch(l){throw A(e,n,l),new c("not_found",`provider "${e}" has no valid bundle for major ${n}`,{param:"version"})}B.set(a,{stamp:f,bundle:d})}return{id:e,major:n,provider:d.provider,commands:d.commands,transport:d.transport.kind==="machine"?{kind:"machine"}:{kind:"http",endpoint:d.transport.endpoint},source:"pinned"}}i(Fe,"resolveProviderBundle");let $=null,D=!1;async function Se(){if(U("user").runtime?.issuer){for(const e of ve(fe()))try{await C(e,!0)}catch(n){n instanceof Object&&_.has(n)||Z(e,void 0,n instanceof Error?n.message:String(n))}for(const[e,n]of ye())try{await R(e,n,!0)}catch(r){if(r instanceof c&&r.code==="provider_version_retired"||r instanceof Object&&_.has(r))continue;Z(e,n,r instanceof Error?r.message:String(r))}}}i(Se,"syncOnce");function Ge(){return $!==null?(D=!0,$):($=(async()=>{try{do D=!1,await Se();while(D)}finally{$=null}})(),$)}i(Ge,"syncProviderBundles");export{We as ensureProviderBundle,ve as providerIdsFromGrants,ge as providerInstallState,ze as refreshProviderBundle,Fe as resolveProviderBundle,Ge as syncProviderBundles,we as unavailableProviderError};
|
|
@@ -18,6 +18,8 @@ export interface ProviderToolDescription {
|
|
|
18
18
|
status: 'available';
|
|
19
19
|
major: number;
|
|
20
20
|
display_name: string;
|
|
21
|
+
/** `machine`: calls go to one of the account's device machines, so its leaves take `--machine`. */
|
|
22
|
+
transport?: 'http' | 'machine';
|
|
21
23
|
groups: Array<{
|
|
22
24
|
group: string;
|
|
23
25
|
leaves: Array<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var V=Object.defineProperty;var c=(e,r)=>V(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as W}from"../../api/command-manifest/result.js";import{callerAllows as S,requireGroup as X}from"../../daemon/api/handlers/providers.js";import{grantFor as Y,terminalGrant as Z}from"../grants/mirror.js";import{assertUnderCap as B,UNCAPPED_GRANTEE as ee}from"../usage/ledger.js";import{listInstalledPlugins as re}from"../resolver.js";import{ProviderError as u}from"./errors.js";import{ensureProviderBundle as oe,refreshProviderBundle as te,resolveProviderBundle as M,unavailableProviderError as ne}from"./bundles.js";import{prepareFileCall as ie,resignForRetry as D,resolveFileResult as se}from"./files.js";import{consumeApproval as ae,requireProviderApproval as de}from"./approval.js";import{outboundToken as pe}from"./mint.js";import{discardFile as ue}from"./token-cache.js";import{registerProviderCall as le}from"./inflight.js";import{buildRequestPlan as ce}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as fe,ProviderTransportError as me}from"./request.js";import{acceptProviderReceipt as ge}from"./receipts.js";import{writeProviderAudit as ve}from"./audit.js";function O(e,r){const n=[],o=c((i,a)=>{const d=[...a,i.name];if(i.kind==="branch"){for(const v of i.children)o(v,d);return}const l=d[0]===r?d.slice(1).join(" "):d.join(" ");n.push({tool:l,leaf:i})},"walk");for(const i of e.roots)o(i,[]);return n}c(O,"providerLeaves");function J(e,r,n){if(e.kind!=="branch")return null;const o=[];for(const h of e.children)if(h.kind==="branch"){const w=J(h,r,n);w&&o.push(w)}else if("group"in h&&h.group&&S(n,`${r}:${h.group}`)){const{kind:w,name:A,description:t,whenToUse:k,tier:m,summary:q,params:_,output:j,effects:$,group:f}=h;o.push({kind:w,name:A,description:t,whenToUse:k,...m===void 0?{}:{tier:m},summary:q,params:_,output:j,effects:$,group:f})}if(o.length===0)return null;const{kind:i,name:a,description:d,whenToUse:l,tier:v,summary:g,model:b,rootEntry:y}=e;return{kind:i,name:a,description:d,whenToUse:l,...v===void 0?{}:{tier:v},summary:g,...b===void 0?{}:{model:b},...y===void 0?{}:{rootEntry:y},children:o}}c(J,"providerHelpBranch");async function Be(e,r){const n=await M(r),o=O(n.commands,r),i=re("user").find(a=>a.name===r)?.manifest.description??r;return{provider:r,status:"available",major:n.major,display_name:i,groups:n.provider.groups.filter(a=>S(e,`${r}:${a.group}`)).map(a=>({group:a.group,leaves:o.filter(({leaf:d})=>d.group===a.group).map(({tool:d,leaf:l})=>({tool:d,description:l.description,summary:l.summary,params:l.params,output:l.output}))})),commands:n.commands.roots.flatMap(a=>{const d=J(a,r,e);return d?[d]:[]})}}c(Be,"providerTools");function he(e){return ge(e.headers["crtr-receipt"],e)}c(he,"onProviderResponse");function G(e){if(!(e.class==="person"||!e.sub||!e.grantee))return{grantee:e.grantee,spend_cap:Y(e.grantee,e.sub)?.spend_cap??null}}c(G,"capTarget");function we(e){if(e.class==="person"){const r=Z()?.sub;if(!r)throw new u("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new u("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}c(we,"identity");function _e(e,r){return[`${e.id}:${r.group}`]}c(_e,"callScope");function be(e,r){const n=O(e.commands,e.id).find(o=>o.tool===r);if(!n)throw new u("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new u("not_found",`tool "${r}" has no group`,{param:"tool"});return n.leaf}c(be,"leafFor");function H(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}c(H,"errorBody");function ye(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const n=Number(r);if(Number.isFinite(n)&&n>=0)return n;const o=Date.parse(r);return Number.isFinite(o)?Math.max(0,Math.ceil((o-Date.now())/1e3)):void 0}c(ye,"retryAfter");function L(e,r,n,o=!1){const i=H(r),a=typeof i.code=="string"?i.code:void 0,d=typeof i.message=="string"?i.message:`provider answered HTTP ${e}`,l=typeof i.field=="string"?i.field:void 0,g={origin:"provider",details:{code:a??null,message:d,...l?{field:l}:{},...typeof i.next=="string"?{next:i.next}:{},...i.received!==void 0?{received:i.received}:{},status:e},...l?{param:l}:{}};return e===429?new u("provider_rate_limited",d,{...g,retry_after_s:ye(n["retry-after"])}):e===401||e>=500?new u("provider_unavailable",d,g):e===403&&a==="provider_refused"?new u("provider_refused",d,g):e===403&&a==="approval_required"?new u("provider_error",o?"provider asked for approval again":"provider approval question is invalid",{...g,status:e}):new u("provider_error",d,{...g,status:e})}c(L,"mapProviderResponse");function z(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.endpoint,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}c(z,"approvalShape");async function De(e,r,n){const o=new AbortController,i=c(()=>o.abort(n.signal.reason),"abort");n.signal.aborted?i():n.signal.addEventListener("abort",i,{once:!0});let a=n.requestId;const d=le({request_id:a,run_id:e.run_id,node_id:e.node_id,controller:o});let l,v,g,b,y,h,w,A="allowed";try{try{await oe(r.provider)}catch{throw ne(r.provider)}let t=await M(r.provider,r.version),k=!1,m,q,_,j=!1,$=!1;for(;;){const f=be(t,r.tool);g=`${t.id}:${f.group}`,X(e,t.id,f.group),B(G(e));let F=null;if(r.approval&&!j){const s=ae(e,r,z(t,f));_=s.option,F=s.prepared,w=r.approval,a=s.requestId,j=!0}const R={params:f.params.filter(s=>"type"in s&&s.type==="file").map(s=>s.name),output:f.output.find(s=>s.type==="file")?.name??null},U=JSON.stringify(R);if(q!==void 0&&q!==U)throw new u("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});q=U,F&&(m=await D(e,R,r.arguments,F,{signal:o.signal})),m??=await ie(e,R,r.arguments,{signal:o.signal});const{sub:I,grantee:x}=we(e);B(G(e));const E=await pe({audience:t.transport.endpoint,sub:I,grantee:x,initiating_app:x,scope:_e(t,f)},{signal:o.signal});b=E.jti;const K={registration:{name:t.id,endpoint:t.transport.endpoint},...t.commands.baseUrl?{baseUrl:t.commands.baseUrl}:{},...t.commands.timeouts?{timeouts:t.commands.timeouts}:{},params:f.params,rest:f.rest,output:f.output,commandPath:[t.id,...r.tool.split(" ")]},Q=ce(K,m.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:a,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},..._?{"Crtr-Approval":_}:{},...m.headers}});v??=Date.now()+(t.commands.timeouts?.requestMs??3e4);let p;try{if(p=await fe(Q,{connectMs:t.commands.timeouts?.connectMs,requestMs:t.commands.timeouts?.requestMs,deadlineAt:v,signal:o.signal}),o.signal.aborted)throw o.signal.reason??new Error("provider call aborted")}catch(s){throw o.signal.aborted?o.signal.reason??s:s instanceof me?new u("provider_unavailable",s.message,{origin:"provider",details:{status:null,code:s.kind}}):s}if(y??=he({provider:t.id,tool:r.tool,request_id:a,run_id:e.run_id,sub:I,grantee:x,ledger_grantee:e.class==="person"?ee:x,jti:E.jti,status:p.status,headers:p.headers}),h=p.headers["crtr-responder"],p.status===401&&ue(E.key),p.status>=200&&p.status<300){const s=p.body;if(!s||typeof s!="object"||Array.isArray(s))throw new u("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:p.status,message:"provider result is not a JSON object"}});let T;try{T=await se(e,m,s,{signal:o.signal})}catch(P){throw P instanceof u&&P.code==="provider_error"?new u("provider_bad_response",P.message,{origin:"provider",details:{status:p.status,message:P.message}}):P}l=T.cleanup;const C=W(f.output,T.result);if(C)throw new u("provider_bad_response",`provider result has an invalid output field "${C.field}"`,{origin:"provider",param:C.field,details:{status:p.status,...C}});if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");return{status:200,body:T.result}}const N=H(p.body);if(p.status===403&&N.code==="approval_required"&&!_&&!$){$=!0;const s=await de(e,r,a,N.question,o.signal,z(t,f),m);if(w=s.questionId,_=s.option,m=await D(e,R,r.arguments,m,{signal:o.signal}),v=void 0,o.signal.aborted)throw new u("user_denied","the approval wait was canceled");continue}if(p.status===403&&N.code==="approval_required"&&_)throw L(p.status,p.body,p.headers,!0);if(p.status!==401&&N.manifest_stale===!0&&!k){k=!0,await te(t.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}}),t=await M(t.id,r.version);continue}throw L(p.status,p.body,p.headers,!!_)}}catch(t){throw t instanceof u&&t.question_id&&(w=t.question_id),A=`refused:${t instanceof u?t.code:"server_error"}`,l?.(),t}finally{try{ve({request_id:a,caller:e,provider:r.provider,tool:r.tool,scope_checked:g,outcome:A,jti:b,receipt_id:y,responder:h,question_id:w})}finally{d()}n.signal.removeEventListener("abort",i)}}c(De,"callProvider");export{De as callProvider,L as mapProviderResponse,he as onProviderResponse,O as providerLeaves,Be as providerTools};
|
|
1
|
+
var Z=Object.defineProperty;var g=(e,r)=>Z(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as x}from"../../api/command-manifest/result.js";import{callerAllows as H,requireGroup as ee}from"../../daemon/api/handlers/providers.js";import{grantFor as re,terminalGrant as te}from"../grants/mirror.js";import{assertUnderCap as L,UNCAPPED_GRANTEE as oe}from"../usage/ledger.js";import{listInstalledPlugins as ne}from"../resolver.js";import{ProviderError as p}from"./errors.js";import{ensureProviderBundle as se,refreshProviderBundle as ie,resolveProviderBundle as S,unavailableProviderError as ae}from"./bundles.js";import{prepareFileCall as de,resignForRetry as G,resolveFileResult as z}from"./files.js";import{consumeApproval as ue,requireProviderApproval as pe}from"./approval.js";import{outboundToken as le}from"./mint.js";import{discardFile as ce}from"./token-cache.js";import{registerProviderCall as fe}from"./inflight.js";import{buildRequestPlan as me}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as ge,ProviderTransportError as ve}from"./request.js";import{acceptProviderReceipt as he}from"./receipts.js";import{writeProviderAudit as we}from"./audit.js";import{selectDevice as be,sendDeviceCall as ye,deviceFailure as _e,deviceUnreachable as Ae,DEFAULT_DEVICE_REQUEST_MS as je}from"./device.js";import{envelopeForProviderCaller as ke}from"../peer/envelope.js";function Q(e,r){const a=[],n=g((i,s)=>{const u=[...s,i.name];if(i.kind==="branch"){for(const v of i.children)n(v,u);return}const t=u[0]===r?u.slice(1).join(" "):u.join(" ");a.push({tool:t,leaf:i})},"walk");for(const i of e.roots)n(i,[]);return a}g(Q,"providerLeaves");function V(e,r,a){if(e.kind!=="branch")return null;const n=[];for(const m of e.children)if(m.kind==="branch"){const f=V(m,r,a);f&&n.push(f)}else if("group"in m&&m.group&&H(a,`${r}:${m.group}`)){const{kind:f,name:A,description:o,whenToUse:P,tier:h,summary:j,params:_,output:$,effects:E,group:k}=m;n.push({kind:f,name:A,description:o,whenToUse:P,...h===void 0?{}:{tier:h},summary:j,params:_,output:$,effects:E,group:k})}if(n.length===0)return null;const{kind:i,name:s,description:u,whenToUse:t,tier:v,summary:c,model:y,rootEntry:b}=e;return{kind:i,name:s,description:u,whenToUse:t,...v===void 0?{}:{tier:v},summary:c,...y===void 0?{}:{model:y},...b===void 0?{}:{rootEntry:b},children:n}}g(V,"providerHelpBranch");async function We(e,r){const a=await S(r),n=Q(a.commands,r),i=ne("user").find(s=>s.name===r)?.manifest.description??r;return{provider:r,status:"available",major:a.major,display_name:i,transport:a.transport.kind,groups:a.provider.groups.filter(s=>H(e,`${r}:${s.group}`)).map(s=>({group:s.group,leaves:n.filter(({leaf:u})=>u.group===s.group).map(({tool:u,leaf:t})=>({tool:u,description:t.description,summary:t.summary,params:t.params,output:t.output}))})),commands:a.commands.roots.flatMap(s=>{const u=V(s,r,e);return u?[u]:[]})}}g(We,"providerTools");function qe(e){return he(e.headers["crtr-receipt"],e)}g(qe,"onProviderResponse");function K(e){if(!(e.class==="person"||!e.sub||!e.grantee))return{grantee:e.grantee,spend_cap:re(e.grantee,e.sub)?.spend_cap??null}}g(K,"capTarget");function Pe(e){if(e.class==="person"){const r=te()?.sub;if(!r)throw new p("grant_revoked","This machine has no active Northlight grant for your terminal. Run `crtr sys join` again from /account/machines.");return{sub:r,grantee:r}}if(!e.sub||!e.grantee)throw new p("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}g(Pe,"identity");function $e(e,r){return[`${e.id}:${r.group}`]}g($e,"callScope");function Ee(e,r){const a=Q(e.commands,e.id).find(n=>n.tool===r);if(!a)throw new p("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!a.leaf.group)throw new p("not_found",`tool "${r}" has no group`,{param:"tool"});return a.leaf}g(Ee,"leafFor");function U(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}g(U,"errorBody");function Re(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const a=Number(r);if(Number.isFinite(a)&&a>=0)return a;const n=Date.parse(r);return Number.isFinite(n)?Math.max(0,Math.ceil((n-Date.now())/1e3)):void 0}g(Re,"retryAfter");function O(e,r,a,n=!1){const i=U(r),s=typeof i.code=="string"?i.code:void 0,u=typeof i.message=="string"?i.message:`provider answered HTTP ${e}`,t=typeof i.field=="string"?i.field:void 0,c={origin:"provider",details:{code:s??null,message:u,...t?{field:t}:{},...typeof i.next=="string"?{next:i.next}:{},...i.received!==void 0?{received:i.received}:{},status:e},...t?{param:t}:{}};return e===429?new p("provider_rate_limited",u,{...c,retry_after_s:Re(a["retry-after"])}):e===401||e>=500?new p("provider_unavailable",u,c):e===403&&s==="provider_refused"?new p("provider_refused",u,c):e===403&&s==="approval_required"?new p("provider_error",n?"provider asked for approval again":"provider approval question is invalid",{...c,status:e}):new p("provider_error",u,{...c,status:e})}g(O,"mapProviderResponse");function W(e,r){return JSON.stringify({major:e.major,endpoint:e.transport.kind==="http"?e.transport.endpoint:null,baseUrl:e.commands.baseUrl??null,rest:r.rest,params:r.params,output:r.output})}g(W,"approvalShape");async function Xe(e,r,a){const n=new AbortController,i=g(()=>n.abort(a.signal.reason),"abort");a.signal.aborted?i():a.signal.addEventListener("abort",i,{once:!0});let s=a.requestId;const u=fe({request_id:s,run_id:e.run_id,node_id:e.node_id,controller:n});let t,v,c,y,b,m,f,A="allowed";try{try{await se(r.provider)}catch{throw ae(r.provider)}let o=await S(r.provider,r.version),P=!1,h,j,_,$=!1,E=!1,k,B=0;for(;;){const w=Ee(o,r.tool);if(c=`${o.id}:${w.group}`,ee(e,o.id,w.group),L(K(e)),o.transport.kind!=="machine"&&r.machine!==void 0)throw new p("invalid_argument",`provider "${o.id}" is not served by a device, so it takes no machine`,{param:"machine"});o.transport.kind==="machine"&&k===void 0&&(k=await be(r.machine),B=Date.now()+(o.commands.timeouts?.requestMs??je));let I=null;if(r.approval&&!$){const d=ue(e,r,W(o,w));_=d.option,I=d.prepared,f=r.approval,s=d.requestId,$=!0}const R={params:w.params.filter(d=>"type"in d&&d.type==="file").map(d=>d.name),output:w.output.find(d=>d.type==="file")?.name??null},J=JSON.stringify(R);if(j!==void 0&&j!==J)throw new p("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});j=J,I&&(h=await G(e,R,r.arguments,I,{signal:n.signal})),h??=await de(e,R,r.arguments,{signal:n.signal});const{sub:M,grantee:T}=Pe(e);if(L(K(e)),o.transport.kind==="machine"){const d=await Te(e,o,r,w,h,{sub:M,requestId:s,deadlineAt:B,signal:n.signal,machine:k});return m=d.responder,t=d.cleanup,{status:200,body:d.result}}const F=await le({audience:o.transport.endpoint,sub:M,grantee:T,initiating_app:T,scope:$e(o,w)},{signal:n.signal});y=F.jti;const X={registration:{name:o.id,endpoint:o.transport.endpoint},...o.commands.baseUrl?{baseUrl:o.commands.baseUrl}:{},...o.commands.timeouts?{timeouts:o.commands.timeouts}:{},params:w.params,rest:w.rest,output:w.output,commandPath:[o.id,...r.tool.split(" ")]},Y=me(X,h.arguments,void 0,{authorization:`Bearer ${F.access_token}`,requestId:s,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},..._?{"Crtr-Approval":_}:{},...h.headers}});v??=Date.now()+(o.commands.timeouts?.requestMs??3e4);let l;try{if(l=await ge(Y,{connectMs:o.commands.timeouts?.connectMs,requestMs:o.commands.timeouts?.requestMs,deadlineAt:v,signal:n.signal}),n.signal.aborted)throw n.signal.reason??new Error("provider call aborted")}catch(d){throw n.signal.aborted?n.signal.reason??d:d instanceof ve?new p("provider_unavailable",d.message,{origin:"provider",details:{status:null,code:d.kind}}):d}if(b??=qe({provider:o.id,tool:r.tool,request_id:s,run_id:e.run_id,sub:M,grantee:T,ledger_grantee:e.class==="person"?oe:T,jti:F.jti,status:l.status,headers:l.headers}),m=l.headers["crtr-responder"],l.status===401&&ce(F.key),l.status>=200&&l.status<300){const d=l.body;if(!d||typeof d!="object"||Array.isArray(d))throw new p("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:l.status,message:"provider result is not a JSON object"}});let C;try{C=await z(e,h,d,{signal:n.signal})}catch(q){throw q instanceof p&&q.code==="provider_error"?new p("provider_bad_response",q.message,{origin:"provider",details:{status:l.status,message:q.message}}):q}t=C.cleanup;const D=x(w.output,C.result);if(D)throw new p("provider_bad_response",`provider result has an invalid output field "${D.field}"`,{origin:"provider",param:D.field,details:{status:l.status,...D}});if(n.signal.aborted)throw n.signal.reason??new Error("provider call aborted");return{status:200,body:C.result}}const N=U(l.body);if(l.status===403&&N.code==="approval_required"&&!_&&!E){E=!0;const d=await pe(e,r,s,N.question,n.signal,W(o,w),h);if(f=d.questionId,_=d.option,h=await G(e,R,r.arguments,h,{signal:n.signal}),v=void 0,n.signal.aborted)throw new p("user_denied","the approval wait was canceled");continue}if(l.status===403&&N.code==="approval_required"&&_)throw O(l.status,l.body,l.headers,!0);if(l.status!==401&&N.manifest_stale===!0&&!P){P=!0,await ie(o.id,{conditional:!1,...r.version===void 0?{}:{version:r.version}}),o=await S(o.id,r.version);continue}throw O(l.status,l.body,l.headers,!!_)}}catch(o){throw o instanceof p&&o.question_id&&(f=o.question_id),m===void 0&&o instanceof p&&typeof o.responder=="string"&&(m=o.responder),A=`refused:${o instanceof p?o.code:"server_error"}`,t?.(),o}finally{try{we({request_id:s,caller:e,provider:r.provider,tool:r.tool,scope_checked:c,outcome:A,jti:y,receipt_id:b,responder:m,question_id:f})}finally{u()}a.signal.removeEventListener("abort",i)}}g(Xe,"callProvider");async function Te(e,r,a,n,i,s){const{machine:u}=s,t=await ye(u,{tool:a.tool,arguments:i.arguments,version:r.major,requestId:s.requestId,runId:e.run_id,deadlineAt:s.deadlineAt,upload:i.upload===null?null:{id:i.upload.upload_id,link:i.upload.upload_link}},ke(e,s.sub),s.signal);if(s.signal.aborted)throw s.signal.reason??new Error("provider call aborted");if("unreachable"in t)throw Ae(u,t.unreachable.reason,t.unreachable.message);const v=t.headers["crtr-responder"],c=U(t.body);if(t.status<200||t.status>=300){const f=_e(u,t.status,typeof c.code=="string"?c.code:void 0,typeof c.message=="string"?c.message:void 0,t.body),A=f===null&&t.status>=500&&typeof c.code=="string"?new p("provider_error",typeof c.message=="string"?c.message:`the device answered HTTP ${t.status}`,{origin:"runtime",status:t.status,details:{code:c.code,status:t.status}}):null;throw Object.assign(f??A??O(t.status,t.body,t.headers),{responder:v})}const y=t.body&&typeof t.body=="object"&&!Array.isArray(t.body)?t.body.result:void 0;if(!y||typeof y!="object"||Array.isArray(y))throw Object.assign(new p("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:t.status,message:"the device result is not a JSON object"}}),{responder:v});let b;try{b=await z(e,i,y,{signal:s.signal})}catch(f){throw f instanceof p&&f.code==="provider_error"?Object.assign(new p("provider_bad_response",f.message,{origin:"provider",details:{status:t.status,message:f.message}}),{responder:v}):f}const m=x(n.output,b.result);if(m)throw b.cleanup?.(),Object.assign(new p("provider_bad_response",`the device result has an invalid output field "${m.field}"`,{origin:"provider",param:m.field,details:{status:t.status,...m}}),{responder:v});return{result:b.result,responder:v,cleanup:b.cleanup}}g(Te,"callDevice");export{Xe as callProvider,O as mapProviderResponse,qe as onProviderResponse,Q as providerLeaves,We as providerTools};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Machine } from '../peer/directory.js';
|
|
2
|
+
import { type PeerResult, type PeerRequest, type PeerOptions } from '../peer/client.js';
|
|
3
|
+
import type { Envelope } from '../peer/envelope.js';
|
|
4
|
+
import { ProviderError } from './errors.js';
|
|
5
|
+
export interface DeviceDeps {
|
|
6
|
+
list(): Promise<Machine[]>;
|
|
7
|
+
/** The last list the directory answered, however old; null before the first. Used only when the directory cannot be reached now. */
|
|
8
|
+
lastList(): Machine[] | null;
|
|
9
|
+
call(machineId: string, request: PeerRequest, options: PeerOptions): Promise<PeerResult>;
|
|
10
|
+
}
|
|
11
|
+
/** Tests replace `current`. */
|
|
12
|
+
export declare const deviceDeps: {
|
|
13
|
+
current: DeviceDeps;
|
|
14
|
+
};
|
|
15
|
+
/** The device call's path on the Desktop (wire contract §5). */
|
|
16
|
+
export declare const DEVICE_CALL_PATH = "/v1/device/call";
|
|
17
|
+
/** The bundle's `timeouts.requestMs` when it declares none (device spec "Per-call deadline"). */
|
|
18
|
+
export declare const DEFAULT_DEVICE_REQUEST_MS = 60000;
|
|
19
|
+
/** The §2.1 selection table. `named` is the caller's `machine`, prefix optional. */
|
|
20
|
+
export declare function selectDevice(named: string | undefined, deps?: DeviceDeps): Promise<Machine>;
|
|
21
|
+
export interface DeviceRequest {
|
|
22
|
+
tool: string;
|
|
23
|
+
arguments: Record<string, unknown>;
|
|
24
|
+
version: number;
|
|
25
|
+
requestId: string;
|
|
26
|
+
runId: string | null;
|
|
27
|
+
deadlineAt: number;
|
|
28
|
+
upload: {
|
|
29
|
+
id: string;
|
|
30
|
+
link: string;
|
|
31
|
+
} | null;
|
|
32
|
+
}
|
|
33
|
+
/** Wire contract §5 body. The upload link travels here, never in a header. */
|
|
34
|
+
export declare function deviceBody(request: DeviceRequest): Record<string, unknown>;
|
|
35
|
+
export declare function sendDeviceCall(machine: Machine, request: DeviceRequest, envelope: Envelope, signal: AbortSignal, deps?: DeviceDeps): Promise<PeerResult>;
|
|
36
|
+
/** What a peer that never answered means for a device call. */
|
|
37
|
+
export declare function deviceUnreachable(machine: Machine, reason: string, message: string): ProviderError;
|
|
38
|
+
/** The device's (or relay's) non-2xx answer as the error `provider.call` returns, or null when the generic provider mapping should handle it. */
|
|
39
|
+
export declare function deviceFailure(machine: Machine, status: number, code: string | undefined, message: string | undefined, body: unknown): ProviderError | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var _=Object.defineProperty;var a=(e,i)=>_(e,"name",{value:i,configurable:!0});import{directory as c,normalizeMachineId as h}from"../peer/directory.js";import{peerCall as m}from"../peer/client.js";import{ProviderError as t}from"./errors.js";const l={current:{list:a(()=>c.list(),"list"),lastList:a(()=>c.lastList(),"lastList"),call:a((e,i,n)=>m(e,i,n),"call")}},p="/v1/device/call",D=6e4,f=a(e=>({machine_id:e.machine_id,name:e.name,last_seen:e.last_seen}),"candidateView");function s(e,i){const n=e.map(r=>`${r.name} (${r.machine_id})`).join(", ");return new t("machine_required",i===void 0?`This account has ${e.length} devices: ${n}. Pick one with --machine <machine_id>.`:`${i} is not one of this account's devices. Devices: ${n}. Pick one with --machine <machine_id>.`,{param:"machine",details:{candidates:e.map(f)}})}a(s,"machineRequired");async function x(e,i=l.current){let n;try{n=await i.list()}catch(o){const d=i.lastList();if(d===null)throw new t("provider_unavailable",`The directory could not be reached to find your devices: ${o instanceof Error?o.message:String(o)}`,{origin:"directory"});n=d}const r=n.filter(o=>o.kind==="device");if(r.length===0)throw new t("provider_not_connected","No device is connected to this account. Install Northlight Desktop on your Mac and sign in.",{origin:"directory"});if(e!==void 0){const o=h(e),d=r.find(u=>u.machine_id===o);if(!d)throw s(r,e);return d}if(r.length>1)throw s(r,void 0);return r[0]}a(x,"selectDevice");function v(e){return{tool:e.tool,arguments:e.arguments,version:e.version,request_id:e.requestId,...e.runId===null?{}:{run_id:e.runId},deadline_at:e.deadlineAt,...e.upload===null?{}:{upload:e.upload}}}a(v,"deviceBody");async function A(e,i,n,r,o=l.current){const d=Math.max(1,i.deadlineAt-Date.now());return o.call(e.machine_id,{method:"POST",path:p,body:v(i)},{envelope:n,device:!0,signal:AbortSignal.any([r,AbortSignal.timeout(d)])})}a(A,"sendDeviceCall");function E(e,i,n){switch(i){case"timeout":return new t("device_timeout",`${e.name} did not answer in time.`,{origin:"runtime",details:{machine_id:e.machine_id}});case"directory_unreachable":case"no_directory":return new t("provider_unavailable",n,{origin:"directory"});case"unknown_machine":return new t("provider_not_connected",`${e.name} is no longer on this account.`,{origin:"directory"});case"not_a_crtr_peer":return new t("server_error",n);default:return new t("device_offline",`${e.name} is offline. ${n}`.trim(),{origin:"runtime",details:{machine_id:e.machine_id}})}}a(E,"deviceUnreachable");function b(e,i,n,r,o){if(i===401)return new t("device_offline",`${e.name}: Desktop is signed out. Sign in to Northlight Desktop again.`,{origin:"runtime",details:{machine_id:e.machine_id}});if(i===426){const d=o?.error?.details?.supported;return new t("peer_version_too_old",r??`${e.name} does not serve this device major. Update Northlight Desktop.`,{origin:"runtime",details:{machine_id:e.machine_id,...Array.isArray(d)?{supported:d}:{}}})}return i===502&&n==="machine_disconnected"?new t("device_disconnected",`${e.name} disconnected during the call; the action may have run.`,{origin:"runtime",details:{machine_id:e.machine_id}}):n==="payload_too_large"||n==="response_too_large"?new t("provider_error",r??"The call or its answer was too large for the relay (16 MiB).",{origin:"runtime",status:413,details:{code:n,message:r??null,status:i}}):null}a(b,"deviceFailure");export{D as DEFAULT_DEVICE_REQUEST_MS,p as DEVICE_CALL_PATH,v as deviceBody,l as deviceDeps,b as deviceFailure,E as deviceUnreachable,x as selectDevice,A as sendDeviceCall};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var y=Object.defineProperty;var d=(i,t)=>y(i,"name",{value:t,configurable:!0});import{RunError as _}from"../runs/operations.js";import{errorCodes as r}from"../../api/error-codes.js";const g={provider_error:{type:"invalid_request_error",status:400,retryable:!1},provider_rate_limited:r.provider_rate_limited,provider_unavailable:r.provider_unavailable,provider_refused:r.provider_refused,provider_not_connected:r.provider_not_connected,provider_version_retired:r.provider_version_retired,storage_unavailable:r.storage_unavailable,approval_pending:{...r.approval_pending,user_action:"answer_question"},user_denied:r.user_denied},b={unauthorized:r.unauthorized,grant_revoked:r.grant_revoked,machine_revoked:r.machine_revoked,scope_missing:r.scope_missing,permission_error:r.permission_error,invalid_request:r.invalid_request,not_found:r.not_found,not_implemented:r.not_implemented,runtime_starting:r.runtime_starting,server_error:r.server_error,provider_bad_response:r.provider_bad_response,invalid_argument:r.invalid_argument,machine_required:r.machine_required,device_offline:r.device_offline,device_timeout:r.device_timeout,device_disconnected:r.device_disconnected,peer_version_too_old:r.peer_version_too_old};function a(i,t){return g[i]??b[i]??(Object.prototype.hasOwnProperty.call(r,i)?r[i]:void 0)??{type:t===401?"authentication_error":t===403?"permission_error":t===429?"rate_limit_error":t>=500?"server_error":"invalid_request_error",status:t,retryable:t>=500}}d(a,"codeRow");class u extends _{static{d(this,"ProviderError")}origin;type;retry_after_s;question_id;user_action;run_id;constructor(t,s,n={}){const e=a(t,n.status??500);super(n.status??e.status,t,s,n.param,n.details,n.scope,e.retryable),this.type=e.type,this.origin=n.origin??"runtime",n.retry_after_s!==void 0&&(this.retry_after_s=n.retry_after_s),n.question_id!==void 0&&(this.question_id=n.question_id),n.run_id!==void 0&&(this.run_id=n.run_id),e.user_action!==void 0&&(this.user_action=e.user_action)}}function O(i){return new u("scope_missing",`missing scope ${i}`,{scope:i})}d(O,"scopeMissing");function q(i){if(!(i instanceof Error))return null;const{status:t,code:s,details:n,retryable:e}=i;return typeof t!="number"||typeof s!="string"||t<400||t>599?null:new _(t,s,i.message,void 0,n,void 0,typeof e=="boolean"?e:a(s,t).retryable)}d(q,"shaped");function h(i){if(i.code==="unauthorized"||i.code==="grant_revoked")return"reconnect";if(i.code==="scope_missing"&&i.scope)return`grant_scope:${i.scope}`}d(h,"directoryAction");function k(i,t,s,n){const e=i instanceof _?i:q(i)??new u("server_error","provider request failed"),o=e instanceof u?e:null,v=a(e.code,e.status),c=o?.run_id??n,p=o?.user_action??(e.code==="cap_exhausted"?"raise_cap":s?h(e):void 0),f=e.code==="cap_exhausted"?e.details?.reset_at:void 0,m={code:e.code,message:e.message,...e.details===void 0?{}:{details:e.details},type:o?.type??v.type,...e.param?{param:e.param}:{},origin:o?.origin??"runtime",retryable:e.retryable,...o?.retry_after_s===void 0?{}:{retry_after_s:o.retry_after_s},...typeof f=="string"?{reset_at:f}:{},request_id:t,...c?{run_id:c}:{},...o?.question_id?{question_id:o.question_id}:{},...p?{user_action:p}:{},...e.scope?{scope:e.scope}:{}},l={"Request-Id":t};return o?.retry_after_s!==void 0&&(l["Retry-After"]=String(o.retry_after_s)),{status:e.status,body:{error:m},headers:l}}d(k,"providerErrorBody");export{g as PROVIDER_CODES,u as ProviderError,a as codeRow,k as providerErrorBody,O as scopeMissing};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var y=Object.defineProperty;var o=(e,n)=>y(e,"name",{value:n,configurable:!0});const p="https://json-schema.org/",g=new Set(["const","enum","default","examples"]);function $(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}o($,"isObject");function u(e,n){try{return new URL(e,n)}catch{return null}}o(u,"parseUrl");function a(e){const n=new URL(e.href);return n.hash="",n.href}o(a,"withoutHash");function h(e,n,r){if(Array.isArray(e)){for(const t of e)h(t,n,r);return}if(!$(e))return;let s=n;if(typeof e.$id=="string"){const t=u(e.$id,n);t!==null&&(t.hash===""||t.hash==="#")&&(s=a(t),r.resources.has(s)||r.resources.set(s,e))}typeof e.$anchor=="string"&&r.anchors.set(`${s}#${e.$anchor}`,e),typeof e.$ref=="string"&&r.refs.push({ref:e.$ref,base:s});for(const[t,l]of Object.entries(e))g.has(t)||h(l,s,r)}o(h,"index");function A(e,n){let r=e;for(const s of n.split("/").slice(1)){let t;try{t=decodeURIComponent(s).replaceAll("~1","/").replaceAll("~0","~")}catch{return}if(Array.isArray(r))r=r[Number(t)];else if($(r)&&Object.hasOwn(r,t))r=r[t];else return}return r}o(A,"pointerGet");function b(e){const n=typeof e.$id=="string"?u(e.$id,p):null,r=n===null?p:a(n),s={resources:new Map([[r,e]]),anchors:new Map,refs:[]};h(e,r,s);for(const{ref:t,base:l}of s.refs){const i=u(t,l),c=i===null?"":decodeURIComponent(i.hash.replace(/^#/,"")),d=i===null?void 0:s.resources.get(a(i));let f;if(i===null||d===void 0?f=!1:c===""?f=!0:c.startsWith("/")?f=A(d,c)!==void 0:f=s.anchors.has(`${a(i)}#${c}`),!f){const m=!t.startsWith("#");return`$ref ${JSON.stringify(t)} does not resolve inside the schema. The schema is a single JSON object, so a $ref can only point into itself (\`#/$defs/x\`, \`#anchor\`) or at another schema document bundled under \`$defs\` with its own \`$id\` (give that document an \`$id\` that the $ref's path resolves against, e.g. \`"$defs": { "common": { "$id": "common.schema.json", \u2026 } }\`).${m?" Files are never fetched.":""}`}}return null}o(b,"outputSchemaRefProblem");export{b as outputSchemaRefProblem};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { outputSchemaRefProblem } from './output-schema-refs.js';
|
|
1
2
|
import type { DeclinedResult } from '../canvas/types.js';
|
|
2
3
|
/** The pi tool a node calls to answer its pending structured-output request
|
|
3
4
|
* (registered by canvas-structured-output; `run.get` names it `submitting_result`). */
|
|
@@ -58,12 +59,22 @@ export declare function readOutputRequest(nodeId: string): StructuredOutputState
|
|
|
58
59
|
export declare function readBrokerOutputRequest(nodeId: string): StructuredOutputState;
|
|
59
60
|
/** Reads the pre-request-id sidecar shape for daemon boot adoption only. */
|
|
60
61
|
export declare function readLegacyOutputRequest(nodeId: string): Pick<StructuredOutputRequest, 'mode' | 'schema' | 'requestMessageId'> | null;
|
|
61
|
-
/** Validate a submitted result value against a stored output schema. Returns
|
|
62
|
-
* the
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
62
|
+
/** Validate a submitted result value against a stored output schema, strictly. Returns
|
|
63
|
+
* the value exactly as submitted, or throws an Error whose message lists the per-field
|
|
64
|
+
* failures. Nothing is coerced: unlike a model's tool arguments, a final result is data
|
|
65
|
+
* the requester stores, so `null` is never turned into `0` / `""` / `false`, `"5"` is
|
|
66
|
+
* not turned into `5`, and a null in an optional field is not silently dropped — each is a
|
|
67
|
+
* mismatch the submitter fixes. TypeBox is imported lazily so the daemon and CLI only
|
|
68
|
+
* pay for its module graph on an actual submit. */
|
|
66
69
|
export declare function validateStructuredResult(schema: Record<string, unknown>, value: unknown): Promise<unknown>;
|
|
70
|
+
export { outputSchemaRefProblem };
|
|
71
|
+
/** Why `schema` cannot be accepted as an `output_schema`, or `null` when it can: a `$ref`
|
|
72
|
+
* that resolves nowhere inside it, or a keyword the validator cannot compile (a bad
|
|
73
|
+
* `pattern`). Checked when the schema is admitted, so the failure reaches the requester
|
|
74
|
+
* instead of the submitting agent, which would otherwise be refused on every attempt. */
|
|
75
|
+
export declare function outputSchemaProblem(schema: Record<string, unknown>): Promise<string | null>;
|
|
76
|
+
/** Throws the `bad_output_schema` input error when `outputSchemaProblem` finds one. */
|
|
77
|
+
export declare function assertOutputSchemaAccepted(schema: Record<string, unknown>): Promise<void>;
|
|
67
78
|
/** Persist a validated result atomically (temp-file-plus-rename), so a
|
|
68
79
|
* concurrent reader (Core's result poll peeks this file) never sees a torn
|
|
69
80
|
* write. Returns the result path. */
|