@north-light/cli 0.3.543 → 0.3.544

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 (62) hide show
  1. package/dist/builtin-memory/northlight-apps/build-on-your-app.md +47 -0
  2. package/dist/builtin-memory/northlight-apps/cli.md +3 -3
  3. package/dist/builtin-memory/northlight-apps/coding-agents.md +16 -7
  4. package/dist/builtin-memory/northlight-apps/concepts/apps-and-sign-in.md +0 -4
  5. package/dist/builtin-memory/northlight-apps/concepts/connections.md +0 -4
  6. package/dist/builtin-memory/northlight-apps/concepts/providers.md +0 -4
  7. package/dist/builtin-memory/northlight-apps/concepts/runs.md +0 -4
  8. package/dist/builtin-memory/northlight-apps/concepts/scopes-and-grants.md +0 -4
  9. package/dist/builtin-memory/northlight-apps/concepts/testing-mode.md +0 -4
  10. package/dist/builtin-memory/northlight-apps/get-started.md +56 -0
  11. package/dist/builtin-memory/northlight-apps/guides/go-live.md +1 -1
  12. package/dist/builtin-memory/northlight-apps/guides/manage-your-app.md +4 -4
  13. package/dist/builtin-memory/northlight-apps/how-it-fits.md +1 -1
  14. package/dist/builtin-memory/northlight-apps/know-the-person.md +29 -0
  15. package/dist/builtin-memory/northlight-apps/let-their-ai-use-your-app.md +67 -0
  16. package/dist/builtin-memory/northlight-apps/providers/README.md +1 -1
  17. package/dist/builtin-memory/northlight-apps/providers/context.md +2 -2
  18. package/dist/builtin-memory/northlight-apps/providers/device.md +3 -3
  19. package/dist/builtin-memory/northlight-apps/providers/integrations.md +1 -1
  20. package/dist/builtin-memory/northlight-apps/providers/search.md +1 -1
  21. package/dist/builtin-memory/northlight-apps/providers/sms.md +3 -3
  22. package/dist/builtin-memory/northlight-apps/quickstart.md +3 -17
  23. package/dist/builtin-memory/northlight-apps/reach-people-by-text.md +50 -0
  24. package/dist/builtin-memory/northlight-apps/use-our-ai.md +55 -0
  25. package/dist/builtin-memory/northlight-apps/use-our-apps.md +46 -0
  26. package/dist/core/command-plugins/transport/http-plan.d.ts +2 -0
  27. package/dist/core/command-plugins/transport/http-plan.js +1 -1
  28. package/dist/core/providers/call.js +1 -1
  29. package/dist/daemon/api/app-listener.d.ts +1 -1
  30. package/dist/daemon/api/app-listener.js +13 -13
  31. package/dist/daemon/api/handlers/providers.d.ts +3 -1
  32. package/dist/shared/traceparent.d.ts +6 -0
  33. package/dist/shared/traceparent.js +1 -0
  34. package/docs/apps/build-on-your-app.md +44 -0
  35. package/docs/apps/cli.md +3 -3
  36. package/docs/apps/coding-agents.md +16 -7
  37. package/docs/apps/concepts/apps-and-sign-in.md +0 -4
  38. package/docs/apps/concepts/connections.md +0 -4
  39. package/docs/apps/concepts/providers.md +0 -4
  40. package/docs/apps/concepts/runs.md +0 -4
  41. package/docs/apps/concepts/scopes-and-grants.md +0 -4
  42. package/docs/apps/concepts/testing-mode.md +0 -4
  43. package/docs/apps/get-started.md +47 -0
  44. package/docs/apps/guides/go-live.md +1 -1
  45. package/docs/apps/guides/manage-your-app.md +4 -4
  46. package/docs/apps/how-it-fits.md +1 -1
  47. package/docs/apps/know-the-person.md +27 -0
  48. package/docs/apps/let-their-ai-use-your-app.md +63 -0
  49. package/docs/apps/providers/README.md +1 -1
  50. package/docs/apps/providers/context.md +2 -2
  51. package/docs/apps/providers/device.md +3 -3
  52. package/docs/apps/providers/integrations.md +1 -1
  53. package/docs/apps/providers/search.md +1 -1
  54. package/docs/apps/providers/sms.md +3 -3
  55. package/docs/apps/quickstart.md +3 -17
  56. package/docs/apps/reach-people-by-text.md +47 -0
  57. package/docs/apps/use-our-ai.md +27 -0
  58. package/docs/apps/use-our-apps.md +32 -0
  59. package/docs/cli/join-northlight.md +1 -1
  60. package/package.json +2 -2
  61. package/packages/crouter-identity/package.json +1 -1
  62. package/runtime.lock.json +12 -12
@@ -0,0 +1,50 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Reach people by text, this knowledge should
4
+ be read because Give your app its own iMessage number with Comms. People text
5
+ it, your server answers with an agent run on their runtime, and you can text
6
+ them again later.
7
+ ---
8
+
9
+ # Reach people by text
10
+
11
+ Comms gives your app its own iMessage number, under your app's name and avatar. People text that number, Comms sends each text to your server as a signed webhook, and your server answers with an agent run on the person's own runtime. Once a person has texted you, your app can text them again later, for example from a timer.
12
+
13
+ ```ts
14
+ import {continueInRuntime, formatReply, verifyWebhook} from '@north-light/comms';
15
+
16
+ const event = verifyWebhook(req.headers, rawBody, [process.env.COMMS_WEBHOOK_SECRET!]);
17
+ if (event.type === 'message.received' && event.data.content.type === 'text') {
18
+ const {contact, content} = event.data;
19
+ if (!contact.user) return; // only a sender texting from an Apple ID email has no user
20
+ const client = await oauth.client(contact.user.id); // their Northlight user id, the same as Connection.userId
21
+ const {runId, reply} = await continueInRuntime({client, message: content.text, runId: runs.get(contact.id), idempotencyKey: event.id});
22
+ runs.set(contact.id, runId); // later texts continue the same run
23
+ const {text} = await reply.collect();
24
+ for (const part of formatReply(text)) {
25
+ await comms.sendMessage({environment: event.environment, to: {contact_id: contact.id}, text: part, idempotency_key: `reply:${event.id}:${part.length}`});
26
+ }
27
+ }
28
+ ```
29
+
30
+ `oauth.client` throws `CrouterError` with code `not_connected` when the person has not signed in to your app yet. Answer that with `sendSignInLink(comms, contact, {environment: event.environment})`: Comms texts them a link that signs them in to your app, which lands on your **Sign-in start URL** (console → **Profile** → **Marketplace**).
31
+
32
+ ## The steps
33
+
34
+ The [Comms guide for coding agents](https://comms.northlight.ai/llms.txt) is the whole reference: the webhook events, the send API, its errors, STOP and HELP handling, and the `crtr comms` commands. In short:
35
+
36
+ 1. **Build in fake mode.** `npx @north-light/comms fake --webhook http://localhost:3000/hook` runs a local Comms with no account. `npx @north-light/comms fake send --from +15550001 --text "hi"` plays a person texting you.
37
+ 2. **Register your app on Northlight.** [[northlight-apps/quickstart]] steps 1 to 4: sign up, `npx @north-light/sdk login`, `npx @north-light/sdk register`. Comms signs in with the same app key.
38
+ 3. **Connect the app to Comms.** At `https://comms.northlight.ai`, sign in and choose **Connect an app**. This is done in a browser only.
39
+ 4. **Turn Texting on.** On the app's page there, or with `crtr comms turn-on`: a display name, a square avatar, the HELP text and your test webhook URL. The page shows the test webhook signing secret.
40
+ 5. **Text it from your own phone, in test mode.** Add yourself as a tester (`crtr comms testers add --app-id <id> --phone <number> --label <name>`, or the site), then text `JOIN <code>` to the test number. Test mode is free; the [Comms guide](https://comms.northlight.ai/llms.txt) has the tester limit.
41
+ 6. **Go to production** when you want your app's own number and anyone to text it. Production is paid: add a card on the billing account, and Comms gets your app its own number.
42
+
43
+ Comms is priced per person your app texts with in production each month; fake mode and testing are free. The SDK is [`@north-light/comms`](https://www.npmjs.com/package/@north-light/comms) on npm. Its 0.3.0 release declares `@north-light/sdk` 0.3.457 as an exact optional peer, so in a project pinned to a newer SDK install it with `npm i @north-light/comms --legacy-peer-deps`.
44
+
45
+ ## Rules
46
+
47
+ - **People text first.** Your app can text a person only after they texted your number in that environment, and until they send STOP. A send to anyone else fails `403 not_texted_first`.
48
+ - **Address people by `contact_id` or `user_id`**, never by phone number.
49
+ - **Derive `idempotency_key` from the event you are answering**, so a retried webhook sends nothing twice. Deduplicate deliveries on `event.id`.
50
+ - **Verify the raw body** with `verifyWebhook` before you parse it, and answer `2xx` within 15 seconds.
@@ -0,0 +1,55 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Let your app use our AI, this knowledge
4
+ should be read because Start agent runs on each person's own Northlight
5
+ runtime, with their memory, files and tools, paid by their AI subscription.
6
+ Typed results, streaming, background runs and documents.
7
+ ---
8
+
9
+ # Let your app use our AI
10
+
11
+ Your app starts agents on the person's own Northlight runtime. Each run has their memory, files and tools, works under the scopes they approved, and its model calls are paid by their AI subscription, not by you.
12
+
13
+ ```ts
14
+ import {z} from 'zod';
15
+ import type {ConnectedCrouter} from '@north-light/sdk';
16
+
17
+ const Profile = z.object({
18
+ display_name: z.string(),
19
+ bio: z.string(),
20
+ facts: z.array(z.string()),
21
+ });
22
+
23
+ /** Start a run on the person's runtime and get a value back that was checked against the schema. */
24
+ export async function draftProfile(runtime: ConnectedCrouter) {
25
+ const outcome = await runtime.runs.parse({
26
+ prompt: 'Draft a public profile for me from my notes.',
27
+ scopes: ['crtr:llm'], // submitting a result needs no other scope
28
+ output_schema: Profile,
29
+ });
30
+
31
+ if (outcome.kind === 'result') return outcome.output_parsed; // {display_name, bio, facts}, typed from Profile
32
+ if (outcome.reason === 'declined') {
33
+ // The agent could not honestly satisfy the schema; outcome.details says why.
34
+ throw new Error(`Declined: ${outcome.details?.reason ?? 'no reason given'}`);
35
+ }
36
+ throw new Error(`The run ended without a result: ${outcome.reason}`); // canceled, deadline_exceeded or runtime_error
37
+ }
38
+ ```
39
+
40
+ `runs.parse` starts a run and returns a value checked against your schema. `runs.start` returns at once and lets you follow the run, reply to it or leave it running.
41
+
42
+ ## Guides
43
+
44
+ | Page | What it covers |
45
+ |---|---|
46
+ | [[northlight-apps/concepts/runs]] | Start a run, its status and outcome, a run's own scopes, and how to bound one |
47
+ | [[northlight-apps/guides/typed-results]] | Get a value back with `runs.parse` and a schema; never parse the trace |
48
+ | [[northlight-apps/guides/stream-a-run]] | Show the agent's text and tool calls as they happen |
49
+ | [[northlight-apps/guides/background-runs]] | Learn when a run you did not wait on has ended, with `RunWatcher` or run notices |
50
+ | [[northlight-apps/guides/files-and-documents]] | Download a file from a run, and read the person's public documents |
51
+ | [[northlight-apps/guides/many-users]] | Keep each person's runtime separate when people meet in your app |
52
+
53
+ To schedule work, call `runs.start` from your own timer. A run's agent may also schedule itself with `crtr cron add` when the run holds `crtr:schedule` ([[northlight-apps/concepts/scopes-and-grants]]).
54
+
55
+ An app that pays for its own model calls instead can serve them from its own service with `inferenceEndpoint` from `@north-light/sdk`; see the [release notes](https://sdk.docs.northlight.ai/docs/release-notes) and the [[northlight-apps/reference]].
@@ -0,0 +1,46 @@
1
+ ---
2
+ kind: knowledge
3
+ when-and-why-to-read: When you need Let your app use our apps, this knowledge
4
+ should be read because Call Northlight's providers from your app on the
5
+ person's runtime — texting their phone, web search, their connected accounts,
6
+ their Mac — with providers.tools and providers.call.
7
+ ---
8
+
9
+ # Let your app use our apps
10
+
11
+ Northlight's own apps are providers your app can call on the person's runtime: text their phone, place calls, search the web, open a cloud browser, read and act in the accounts they connected, and use their Mac. The person approves each provider's scope once, on your app's consent page.
12
+
13
+ ```ts
14
+ import type {ConnectedCrouter} from '@north-light/sdk';
15
+
16
+ /** List the tools a provider offers this person, then call one. Scope strings come from the console's
17
+ * "Add permissions" list; tool names and arguments come from `providers.tools`. Never invent either. */
18
+ export async function textMe(runtime: ConnectedCrouter, body: string) {
19
+ const sms = await runtime.providers.tools('sms');
20
+ console.log(JSON.stringify(sms.groups, null, 2)); // each leaf: tool, description, params
21
+
22
+ return runtime.providers.call({provider: 'sms', tool: 'send', arguments: {body}});
23
+ }
24
+ ```
25
+
26
+ Every provider works the same way: ask for its scope, list its tools with `providers.tools('<provider>')`, then call one with `providers.call`. Scope strings, tool names and arguments come from those lists, never from memory.
27
+
28
+ ## Providers
29
+
30
+ | Provider id | What it does | Page |
31
+ |---|---|---|
32
+ | `sms` | Texts the signed-in person's own verified phone | [[northlight-apps/providers/sms]] |
33
+ | `search` | Web search, sourced answers, page contents and paid data endpoints | [[northlight-apps/providers/search]] |
34
+ | `integrations` | The person's own accounts at other services | [[northlight-apps/providers/integrations]] |
35
+ | `context` | Ask about the person from their memory, and look up public profiles | [[northlight-apps/providers/context]] |
36
+ | `device` | The person's Mac: browser, files, Messages and shell | [[northlight-apps/providers/device]] |
37
+
38
+ These five have pages here. Northlight also runs `phone` (calls from Northlight's number, with an AI voice), `browser` (cloud browser sessions) and `meetings` (the person's meeting notes and transcripts), which have no page yet. For any of them, `providers.tools()` with no argument lists every provider installed on the person's runtime, `providers.tools('<provider>')` lists its tools and arguments, and the console's **Add permissions** list shows every scope your app can ask for.
39
+
40
+ ## Guides
41
+
42
+ | Page | What it covers |
43
+ |---|---|
44
+ | [[northlight-apps/concepts/providers]] | Scopes, finding and calling tools, and providers your app requires |
45
+ | [[northlight-apps/providers/README]] | Every first-party provider, its scopes and where names come from |
46
+ | [[northlight-apps/guides/grant-changes]] | When a person adds or removes a scope, and waiting on a provider's setup |
@@ -25,6 +25,8 @@ export interface HttpInvokerSpec {
25
25
  export interface RequestPlanOptions {
26
26
  authorization: string | null;
27
27
  requestId: string;
28
+ /** The trace id a provider call carries on as `traceparent: 00-<trace>-<new parent>-00`; absent sends none. */
29
+ traceId?: string;
28
30
  extraHeaders: Record<string, string>;
29
31
  fileMode: 'read-local' | 'pass-through';
30
32
  }
@@ -1 +1 @@
1
- var R=Object.defineProperty;var c=(e,t)=>R(e,"name",{value:t,configurable:!0});import{readFileSync as O,statSync as k}from"node:fs";import{resolve as j}from"node:path";import{validateDeclaredResult as C}from"../../../api/command-manifest/result.js";import{CrtrError as w}from"../../errors.js";import{envelopeClaimsStale as P,staleErrorDetails as z}from"../../manifest-stale.js";import{ExitCode as h}from"../../../types.js";import{isRecord as x}from"../../../shared/predicates.js";import{trimHeaderValue as U}from"./sign.js";const q=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,T=new Set(["internal","unknown_path","command_collision","cli_protocol_error"]),D=new Set(["authorization","crtr-request-id","crtr-run-id","crtr-approval","crtr-upload-id","crtr-upload-link","accept","content-type","crtr-caller","crtr-signature"]),M=new Set(["crtr-caller","crtr-signature","crtr-request-id"]);function te(e,t,n,r){const i=new Map;for(const o of e.params)i.set(o.name,o);const f=Object.entries(e.rest.params),u=e.baseUrl??new URL(e.registration.endpoint).origin;let y=e.rest.path;for(const[o,a]of f){if(a.in!=="path")continue;const d=t[S(o)];y=y.split(`{${o}}`).join(I(d))}const b=new URL(y,u);for(const[o,a]of f){if(a.in!=="query")continue;const d=S(o);v(i.get(o),d,t,n)&&b.searchParams.append(a.as??o,String(t[d]))}const m=G(e,f,i,t,n,r.fileMode),p=Object.keys(m).length>0?JSON.stringify(m):void 0,l={},s=c((o,a)=>{const d=Object.keys(l).find(g=>g.toLowerCase()===o.toLowerCase());d!==void 0&&delete l[d],l[o]=U(a)},"setHeader");s("Accept","application/json"),s("Crtr-Request-Id",r.requestId),r.authorization!==null&&s("Authorization",r.authorization);for(const[o,a]of f){if(a.in!=="header")continue;const d=S(o);if(!v(i.get(o),d,t,n))continue;const g=a.as;if(M.has(g.toLowerCase())||r.fileMode==="pass-through"&&D.has(g.toLowerCase()))throw new w("invalid_type",`${E(e)}: header "${g}" is reserved for the runtime`,h.USAGE,{field:o,next:"Remove the runtime-owned header mapping from the manifest."});const A=String(t[d]);if(/[\r\n]/.test(A))throw new w("invalid_type",`${E(e)}: header "${g}" value must not contain a newline`,h.USAGE,{field:o,received:"(newline in header value)",next:"Remove newline characters from the value."});s(g,A)}p!==void 0&&s("Content-Type","application/json");for(const[o,a]of Object.entries(r.extraHeaders)){if(r.fileMode==="pass-through"&&/[\r\n]/.test(a))throw new w("invalid_type",`${E(e)}: header "${o}" value must not contain a newline`,h.USAGE);s(o,a)}if(r.fileMode==="pass-through"){if(s("Accept","application/json"),p!==void 0)s("Content-Type","application/json");else{const o=Object.keys(l).find(a=>a.toLowerCase()==="content-type");o!==void 0&&delete l[o]}if(s("Crtr-Request-Id",r.requestId),r.authorization!==null)s("Authorization",r.authorization);else{const o=Object.keys(l).find(a=>a.toLowerCase()==="authorization");o!==void 0&&delete l[o]}}return{url:b,method:e.rest.method,headers:l,...p!==void 0?{body:p}:{}}}c(te,"buildRequestPlan");function G(e,t,n,r,i,f){const u=new Map;for(const[m,p]of t){if(p.in!=="body")continue;const l=p.as??m,s=u.get(l);s!==void 0?s.push(m):u.set(l,[m])}const y={};for(const[m,p]of u){const l=p.map(d=>({name:d,param:n.get(d)})),s=l.find(d=>d.param.kind!=="stdin"),o=l.find(d=>d.param.kind==="stdin");let a;s!==void 0&&v(s.param,S(s.name),r,i)?a={param:s.param,value:r[S(s.name)]}:o!==void 0&&v(o.param,S(o.name),r,i)&&(a={param:o.param,value:r[S(o.name)]}),a!==void 0&&(y[m]=H(e,a.param,a.value,f))}const b={...e.rest.body};return e.rest.bodyRoot!==void 0?b[e.rest.bodyRoot]=y:Object.assign(b,y),b}c(G,"assembleBody");function H(e,t,n,r){return r==="pass-through"||t.kind==="context-file"?n:(t.kind==="positional"||t.kind==="flag")&&t.type==="path"?N(E(e),t,n):n}c(H,"materializeBodyValue");function N(e,t,n){if(Array.isArray(n))return n.map(u=>N(e,t,u));const r=String(n),i=j(process.cwd(),r);let f;try{f=k(i)}catch{throw _(e,t.name,r,"no file exists at that path","an existing local file path")}if(!f.isFile())throw _(e,t.name,r,"the path is not a regular file","a regular local file (not a directory, socket, or device)");try{return"encoding"in t&&t.encoding==="base64"?O(i).toString("base64"):O(i,"utf8")}catch{throw _(e,t.name,r,"the file could not be read","a readable local file path")}}c(N,"readPathParam");function _(e,t,n,r,i){return new w("invalid_type",`${e}: cannot read file for "${t}" \u2014 ${r}: ${n}`,h.USAGE,{field:t,received:n,next:`Pass ${i}, relative to the current directory or absolute.`})}c(_,"fileError");function v(e,t,n,r){return r!==void 0?r.providedParams.has(t):Object.prototype.hasOwnProperty.call(n,t)&&n[t]!==void 0}c(v,"isPresent");function I(e){const t=String(e);return t==="."||t===".."?t.replace(/\./g,"%2E"):encodeURIComponent(t)}c(I,"encodePathSegmentValue");function S(e){return e.replace(/-([a-z])/g,(t,n)=>n.toUpperCase())}c(S,"paramKey");function ne(e,t){let n;try{n=JSON.parse(t)}catch{throw $(e,"response body was not valid JSON","a JSON object matching the declared output")}if(!x(n))throw $(e,`response body was not a JSON object (got ${V(n)})`,"a JSON object matching the declared output");const r=C(e.output,n);if(r!==null){const f=e.output.find(u=>u.name===r.field)?.type??"unknown";throw r.receivedType!==void 0?$(e,`output field "${r.field}" has the wrong type`,`${r.field}: ${r.expected??f}`):$(e,`response is missing required output field "${r.field}"`,`a "${r.field}" field of type ${f}`)}return n}c(ne,"mapUnary2xx");function re(e,t,n){const r=L(t),i=J(n);if(i!==null){const f=q.test(i.code)&&!T.has(i.code);throw new w(f?i.code:"cli_protocol_error",i.message,r,{...i.received!==void 0?{received:i.received}:{},...i.field!==void 0?{field:i.field}:{},...i.manifestStale?z(e.registration.name):{},http_status:t,next:i.next??"Inspect the backend response, then retry if appropriate."})}throw new w("cli_http_error",`${E(e)}: request failed with HTTP ${t}`,r,{http_status:t,next:"Inspect the backend response, then retry if appropriate."})}c(re,"throwNon2xx");function J(e){let t;try{t=JSON.parse(e)}catch{return null}if(!x(t)||!x(t.error))return null;const n=t.error;return typeof n.code!="string"||typeof n.message!="string"?null:{code:n.code,message:n.message,...typeof n.field=="string"?{field:n.field}:{},...n.received!==void 0?{received:n.received}:{},...typeof n.next=="string"?{next:n.next}:{},manifestStale:P(n),...n.question!==void 0?{question:n.question}:{}}}c(J,"parseHttpError");function L(e){return e===400||e===401||e===403||e===422?h.USAGE:e===404?h.NOT_FOUND:e===409?h.AMBIGUOUS:h.GENERAL}c(L,"deriveExit");function $(e,t,n){return new w("cli_protocol_error",`${E(e)}: ${t}. Expected ${n}.`,h.GENERAL,{received:t,next:`Run \`crtr pkg plugin update ${e.registration.name}\` or inspect the backend response.`})}c($,"protocolError");function E(e){return`HTTP-transport plugin "${e.registration.name}" command \`${e.commandPath.join(" ")}\``}c(E,"label");function V(e){return e===null?"null":Array.isArray(e)?"array":typeof e}c(V,"typeName");export{te as buildRequestPlan,ne as mapUnary2xx,J as parseHttpError,N as readPathParam,re as throwNon2xx};
1
+ var R=Object.defineProperty;var c=(e,t)=>R(e,"name",{value:t,configurable:!0});import{readFileSync as O,statSync as k}from"node:fs";import{resolve as j}from"node:path";import{validateDeclaredResult as C}from"../../../api/command-manifest/result.js";import{CrtrError as w}from"../../errors.js";import{envelopeClaimsStale as P,staleErrorDetails as z}from"../../manifest-stale.js";import{ExitCode as h}from"../../../types.js";import{isRecord as x}from"../../../shared/predicates.js";import{trimHeaderValue as U}from"./sign.js";import{outboundTraceparent as q}from"../../../shared/traceparent.js";const T=/^[a-z][a-z0-9]*(_[a-z0-9]+)*$/,I=new Set(["internal","unknown_path","command_collision","cli_protocol_error"]),D=new Set(["authorization","crtr-request-id","crtr-run-id","crtr-approval","crtr-upload-id","crtr-upload-link","accept","content-type","crtr-caller","crtr-signature","traceparent"]),M=new Set(["crtr-caller","crtr-signature","crtr-request-id"]);function ne(e,t,r,n){const i=new Map;for(const o of e.params)i.set(o.name,o);const f=Object.entries(e.rest.params),u=e.baseUrl??new URL(e.registration.endpoint).origin;let y=e.rest.path;for(const[o,a]of f){if(a.in!=="path")continue;const s=t[b(o)];y=y.split(`{${o}}`).join(J(s))}const S=new URL(y,u);for(const[o,a]of f){if(a.in!=="query")continue;const s=b(o);v(i.get(o),s,t,r)&&S.searchParams.append(a.as??o,String(t[s]))}const m=G(e,f,i,t,r,n.fileMode),p=Object.keys(m).length>0?JSON.stringify(m):void 0,l={},d=c((o,a)=>{const s=Object.keys(l).find(g=>g.toLowerCase()===o.toLowerCase());s!==void 0&&delete l[s],l[o]=U(a)},"setHeader");d("Accept","application/json"),d("Crtr-Request-Id",n.requestId),n.authorization!==null&&d("Authorization",n.authorization);for(const[o,a]of f){if(a.in!=="header")continue;const s=b(o);if(!v(i.get(o),s,t,r))continue;const g=a.as;if(M.has(g.toLowerCase())||n.fileMode==="pass-through"&&D.has(g.toLowerCase()))throw new w("invalid_type",`${E(e)}: header "${g}" is reserved for the runtime`,h.USAGE,{field:o,next:"Remove the runtime-owned header mapping from the manifest."});const A=String(t[s]);if(/[\r\n]/.test(A))throw new w("invalid_type",`${E(e)}: header "${g}" value must not contain a newline`,h.USAGE,{field:o,received:"(newline in header value)",next:"Remove newline characters from the value."});d(g,A)}p!==void 0&&d("Content-Type","application/json");for(const[o,a]of Object.entries(n.extraHeaders)){if(n.fileMode==="pass-through"&&/[\r\n]/.test(a))throw new w("invalid_type",`${E(e)}: header "${o}" value must not contain a newline`,h.USAGE);d(o,a)}if(n.fileMode==="pass-through"){if(d("Accept","application/json"),p!==void 0)d("Content-Type","application/json");else{const o=Object.keys(l).find(a=>a.toLowerCase()==="content-type");o!==void 0&&delete l[o]}if(d("Crtr-Request-Id",n.requestId),n.traceId!==void 0&&d("traceparent",q(n.traceId)),n.authorization!==null)d("Authorization",n.authorization);else{const o=Object.keys(l).find(a=>a.toLowerCase()==="authorization");o!==void 0&&delete l[o]}}return{url:S,method:e.rest.method,headers:l,...p!==void 0?{body:p}:{}}}c(ne,"buildRequestPlan");function G(e,t,r,n,i,f){const u=new Map;for(const[m,p]of t){if(p.in!=="body")continue;const l=p.as??m,d=u.get(l);d!==void 0?d.push(m):u.set(l,[m])}const y={};for(const[m,p]of u){const l=p.map(s=>({name:s,param:r.get(s)})),d=l.find(s=>s.param.kind!=="stdin"),o=l.find(s=>s.param.kind==="stdin");let a;d!==void 0&&v(d.param,b(d.name),n,i)?a={param:d.param,value:n[b(d.name)]}:o!==void 0&&v(o.param,b(o.name),n,i)&&(a={param:o.param,value:n[b(o.name)]}),a!==void 0&&(y[m]=H(e,a.param,a.value,f))}const S={...e.rest.body};return e.rest.bodyRoot!==void 0?S[e.rest.bodyRoot]=y:Object.assign(S,y),S}c(G,"assembleBody");function H(e,t,r,n){return n==="pass-through"||t.kind==="context-file"?r:(t.kind==="positional"||t.kind==="flag")&&t.type==="path"?N(E(e),t,r):r}c(H,"materializeBodyValue");function N(e,t,r){if(Array.isArray(r))return r.map(u=>N(e,t,u));const n=String(r),i=j(process.cwd(),n);let f;try{f=k(i)}catch{throw _(e,t.name,n,"no file exists at that path","an existing local file path")}if(!f.isFile())throw _(e,t.name,n,"the path is not a regular file","a regular local file (not a directory, socket, or device)");try{return"encoding"in t&&t.encoding==="base64"?O(i).toString("base64"):O(i,"utf8")}catch{throw _(e,t.name,n,"the file could not be read","a readable local file path")}}c(N,"readPathParam");function _(e,t,r,n,i){return new w("invalid_type",`${e}: cannot read file for "${t}" \u2014 ${n}: ${r}`,h.USAGE,{field:t,received:r,next:`Pass ${i}, relative to the current directory or absolute.`})}c(_,"fileError");function v(e,t,r,n){return n!==void 0?n.providedParams.has(t):Object.prototype.hasOwnProperty.call(r,t)&&r[t]!==void 0}c(v,"isPresent");function J(e){const t=String(e);return t==="."||t===".."?t.replace(/\./g,"%2E"):encodeURIComponent(t)}c(J,"encodePathSegmentValue");function b(e){return e.replace(/-([a-z])/g,(t,r)=>r.toUpperCase())}c(b,"paramKey");function oe(e,t){let r;try{r=JSON.parse(t)}catch{throw $(e,"response body was not valid JSON","a JSON object matching the declared output")}if(!x(r))throw $(e,`response body was not a JSON object (got ${B(r)})`,"a JSON object matching the declared output");const n=C(e.output,r);if(n!==null){const f=e.output.find(u=>u.name===n.field)?.type??"unknown";throw n.receivedType!==void 0?$(e,`output field "${n.field}" has the wrong type`,`${n.field}: ${n.expected??f}`):$(e,`response is missing required output field "${n.field}"`,`a "${n.field}" field of type ${f}`)}return r}c(oe,"mapUnary2xx");function ie(e,t,r){const n=V(t),i=L(r);if(i!==null){const f=T.test(i.code)&&!I.has(i.code);throw new w(f?i.code:"cli_protocol_error",i.message,n,{...i.received!==void 0?{received:i.received}:{},...i.field!==void 0?{field:i.field}:{},...i.manifestStale?z(e.registration.name):{},http_status:t,next:i.next??"Inspect the backend response, then retry if appropriate."})}throw new w("cli_http_error",`${E(e)}: request failed with HTTP ${t}`,n,{http_status:t,next:"Inspect the backend response, then retry if appropriate."})}c(ie,"throwNon2xx");function L(e){let t;try{t=JSON.parse(e)}catch{return null}if(!x(t)||!x(t.error))return null;const r=t.error;return typeof r.code!="string"||typeof r.message!="string"?null:{code:r.code,message:r.message,...typeof r.field=="string"?{field:r.field}:{},...r.received!==void 0?{received:r.received}:{},...typeof r.next=="string"?{next:r.next}:{},manifestStale:P(r),...r.question!==void 0?{question:r.question}:{}}}c(L,"parseHttpError");function V(e){return e===400||e===401||e===403||e===422?h.USAGE:e===404?h.NOT_FOUND:e===409?h.AMBIGUOUS:h.GENERAL}c(V,"deriveExit");function $(e,t,r){return new w("cli_protocol_error",`${E(e)}: ${t}. Expected ${r}.`,h.GENERAL,{received:t,next:`Run \`crtr pkg plugin update ${e.registration.name}\` or inspect the backend response.`})}c($,"protocolError");function E(e){return`HTTP-transport plugin "${e.registration.name}" command \`${e.commandPath.join(" ")}\``}c(E,"label");function B(e){return e===null?"null":Array.isArray(e)?"array":typeof e}c(B,"typeName");export{ne as buildRequestPlan,oe as mapUnary2xx,L as parseHttpError,N as readPathParam,ie as throwNon2xx};
@@ -1 +1 @@
1
- var re=Object.defineProperty;var v=(e,r)=>re(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as I}from"../../api/command-manifest/result.js";import{callerAllows as te,requireGroup as oe}from"../../daemon/api/handlers/providers.js";import{terminalGrant as ie}from"../grants/mirror.js";import{UNCAPPED_GRANTEE as ne}from"../usage/ledger.js";import{listInstalledPlugins as se}from"../resolver.js";import{ProviderError as p}from"./errors.js";import{resolveProviderRef as ae}from"./roots.js";import{providerToolPath as de}from"../plugin-objects-manifest.js";import{ensureProviderBundle as ue,refreshProviderBundle as le,resolveProviderBundle as U,unavailableProviderError as pe}from"./bundles.js";import{prepareFileCall as ce,resignForRetry as G,resolveFileResult as z}from"./files.js";import{consumeApproval as fe,requireProviderApproval as me}from"./approval.js";import{outboundToken as ve}from"./mint.js";import{discardFile as ge}from"./token-cache.js";import{registerProviderCall as he}from"./inflight.js";import{buildRequestPlan as we}from"../command-plugins/transport/http-plan.js";import{sendProviderRequest as be,ProviderTransportError as ye}from"./request.js";import{acceptProviderReceipt as _e}from"./receipts.js";import{writeProviderAudit as Ae}from"./audit.js";import{selectDevice as je,sendDeviceCall as ke,deviceFailure as Pe,deviceUnreachable as Q,DEFAULT_DEVICE_REQUEST_MS as qe}from"./device.js";import{isLibsSearch as V,wantsBundling as Ee,callLibsSearch as $e,bundleExec as Ce,mapLibOutcome as Re,reportLibCalls as Se}from"./device-libs.js";import{directoryIssuer as Ne,ownMachineId as Te}from"../peer/directory.js";import{formatCrtrCaller as K}from"../command-plugins/caller-header.js";import{getNode as De}from"../canvas/canvas.js";import{envelopeForProviderCaller as Fe}from"../peer/envelope.js";function W(e){const r=[],s=v((c,t,o)=>{const a=[...t,c.name],d=o||c.tier==="hidden";if(c.kind==="branch"){for(const m of c.children)s(m,a,d);return}const A=de(e.roots.length,a);r.push({tool:A,leaf:c,hidden:d})},"walk");for(const c of e.roots)s(c,[],!1);return r}v(W,"providerLeaves");function X(e){if(e.kind!=="branch")return null;const r=[];for(const h of e.children)if(h.kind==="branch"){const j=X(h);j&&r.push(j)}else if("group"in h&&h.group){const{kind:j,name:u,description:_,whenToUse:g,tier:w,summary:P,params:i,output:k,effects:n,group:b}=h;r.push({kind:j,name:u,description:_,whenToUse:g,...w===void 0?{}:{tier:w},summary:P,params:i,output:k,effects:n,group:b})}if(r.length===0)return null;const{kind:s,name:c,description:t,whenToUse:o,tier:a,summary:d,model:A,rootEntry:m}=e;return{kind:s,name:c,description:t,whenToUse:o,...a===void 0?{}:{tier:a},summary:d,...A===void 0?{}:{model:A},...m===void 0?{}:{rootEntry:m},children:r}}v(X,"providerHelpBranch");async function mr(e,r){const s=await U(r),c=W(s.commands),t=se("user").find(o=>o.name===r)?.manifest.description??r;return{provider:r,status:"available",major:s.major,display_name:t,transport:s.transport.kind,groups:s.provider.groups.filter(o=>te(e,`${r}:${o.group}`)).map(o=>({group:o.group,leaves:c.filter(({leaf:a,hidden:d})=>!d&&a.group===o.group).map(({tool:a,leaf:d})=>({tool:a,description:d.description,summary:d.summary,params:d.params,output:d.output}))})),commands:s.commands.roots.flatMap(o=>{const a=X(o);return a?[a]:[]})}}v(mr,"providerTools");async function Me(e){return await _e(e.headers["crtr-receipt"],e)}v(Me,"onProviderResponse");async function Oe(e){if(e.class==="person"){const r=(await ie())?.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}}v(Oe,"identity");function Ie(e,r){return[`${e.id}:${r.group}`]}v(Ie,"callScope");function Ue(e,r){const s=W(e.commands).find(c=>c.tool===r);if(!s)throw new p("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!s.leaf.group)throw new p("not_found",`tool "${r}" has no group`,{param:"tool"});return s.leaf}v(Ue,"leafFor");function B(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}v(B,"errorBody");function Be(e){const r=Array.isArray(e)?e[0]:e;if(!r)return;const s=Number(r);if(Number.isFinite(s)&&s>=0)return s;const c=Date.parse(r);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}v(Be,"retryAfter");function D(e,r,s,c=!1){const t=B(r),o=typeof t.code=="string"?t.code:void 0,a=typeof t.message=="string"?t.message:`provider answered HTTP ${e}`,d=typeof t.field=="string"?t.field:void 0,m={origin:"provider",details:{code:o??null,message:a,...d?{field:d}:{},...typeof t.next=="string"?{next:t.next}:{},...t.received!==void 0?{received:t.received}:{},status:e},...d?{param:d}:{}};return e===429?new p("provider_rate_limited",a,{...m,retry_after_s:Be(s["retry-after"])}):e===401||e>=500?new p("provider_unavailable",a,m):e===403&&o==="provider_refused"?new p("provider_refused",a,m):e===403&&o==="approval_required"?new p("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...m,status:e}):new p("provider_error",a,{...m,status:e})}v(D,"mapProviderResponse");function Y(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})}v(Y,"approvalShape");async function Le(e){const r=e.origin!==void 0?K({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):K({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await De(e.node_id))?.profile_id??void 0:void 0,machine:Ne()!==null?Te():void 0});return r===""?{}:{"Crtr-Caller":r}}v(Le,"crtrCallerHeader");async function vr(e,r,s){const c=ae(r.provider);if(c===null)throw new p("not_found",`provider "${r.provider}" is not available`,{param:"provider"});const t={...r,provider:c},o=new AbortController,a=v(()=>o.abort(s.signal.reason),"abort");s.signal.aborted?a():s.signal.addEventListener("abort",a,{once:!0});let d=s.requestId;const A=he({request_id:d,run_id:e.run_id,node_id:e.node_id,controller:o});let m,h,j,u,_,g,w,P="allowed";try{try{await ue(t.provider)}catch{throw pe(t.provider)}let i=await U(t.provider,t.version),k=!1,n,b,q,L=!1,J=!1,F,H=0;for(;;){const y=Ue(i,t.tool);if(j=`${i.id}:${y.group}`,oe(e,i.id,y.group),i.transport.kind!=="machine"&&t.machine!==void 0)throw new p("invalid_argument",`provider "${i.id}" is not served by a device, so it takes no machine`,{param:"machine"});i.transport.kind==="machine"&&F===void 0&&(F=await je(t.machine,void 0,{newest:V(i.major,t.tool)}),H=Date.now()+(i.commands.timeouts?.requestMs??qe));let M=null;if(t.approval&&!L){const l=await fe(e,t,Y(i,y));q=l.option,M=l.prepared,w=t.approval,d=l.requestId,L=!0}const C={params:y.params.filter(l=>"type"in l&&l.type==="file").map(l=>l.name),output:y.output.find(l=>l.type==="file")?.name??null},x=JSON.stringify(C);if(b!==void 0&&b!==x)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"}});b=x,M&&(n=await G(e,C,t.arguments,M,{signal:o.signal})),n??=await ce(e,C,t.arguments,{signal:o.signal});const{sub:O,grantee:R}=await Oe(e);if(i.transport.kind==="machine"){const l=await Je(e,i,t,y,n,{sub:O,requestId:d,deadlineAt:H,signal:o.signal,machine:F});return g=l.responder,m=l.cleanup,{status:200,body:l.result}}const E=await ve({audience:i.transport.endpoint,sub:O,grantee:R,initiating_app:R,scope:Ie(i,y)},{signal:o.signal});u=E.jti;const Z={registration:{name:i.id,endpoint:i.transport.endpoint},...i.commands.baseUrl?{baseUrl:i.commands.baseUrl}:{},...i.commands.timeouts?{timeouts:i.commands.timeouts}:{},params:y.params,rest:y.rest,output:y.output,commandPath:[i.id,...t.tool.split(" ")]},ee=we(Z,n.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:d,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await Le(e),...q?{"Crtr-Approval":q}:{},...n.headers}});h??=Date.now()+(i.commands.timeouts?.requestMs??3e4);let f;try{if(f=await be(ee,{connectMs:i.commands.timeouts?.connectMs,requestMs:i.commands.timeouts?.requestMs,deadlineAt:h,signal:o.signal}),o.signal.aborted)throw o.signal.reason??new Error("provider call aborted")}catch(l){throw o.signal.aborted?o.signal.reason??l:l instanceof ye?new p("provider_unavailable",l.message,{origin:"provider",details:{status:null,code:l.kind}}):l}if(_??=await Me({provider:i.id,tool:t.tool,request_id:d,run_id:e.run_id,sub:O,grantee:R,payer:E.payer,ledger_grantee:e.class==="person"?ne:R,jti:E.jti,status:f.status,headers:f.headers}),g=f.headers["crtr-responder"],f.status===401&&ge(E.key),f.status>=200&&f.status<300){const l=f.body;if(!l||typeof l!="object"||Array.isArray(l))throw new p("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let N;try{N=await z(e,n,l,{signal:o.signal})}catch($){throw $ instanceof p&&$.code==="provider_error"?new p("provider_bad_response",$.message,{origin:"provider",details:{status:f.status,message:$.message}}):$}m=N.cleanup;const T=I(y.output,N.result);if(T)throw new p("provider_bad_response",`provider result has an invalid output field "${T.field}"`,{origin:"provider",param:T.field,details:{status:f.status,...T}});if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");return{status:200,body:N.result}}const S=B(f.body);if(f.status===403&&S.code==="approval_required"&&!q&&!J){J=!0;const l=await me(e,t,d,S.question,o.signal,Y(i,y),n);if(w=l.questionId,q=l.option,n=await G(e,C,t.arguments,n,{signal:o.signal}),h=void 0,o.signal.aborted)throw new p("user_denied","the approval wait was canceled");continue}if(f.status===403&&S.code==="approval_required"&&q)throw D(f.status,f.body,f.headers,!0);if(f.status!==401&&S.manifest_stale===!0&&!k){k=!0,await le(i.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}}),i=await U(i.id,t.version);continue}throw D(f.status,f.body,f.headers,!!q)}}catch(i){throw i instanceof p&&i.question_id&&(w=i.question_id),g===void 0&&i instanceof p&&typeof i.responder=="string"&&(g=i.responder),P=`refused:${i instanceof p?i.code:"server_error"}`,await m?.(),i}finally{try{await Ae({request_id:d,caller:e,provider:t.provider,tool:t.tool,scope_checked:j,outcome:P,jti:u,receipt_id:_,responder:g,question_id:w})}finally{A()}s.signal.removeEventListener("abort",a)}}v(vr,"callProvider");async function Je(e,r,s,c,t,o){const{machine:a}=o,d=await Fe(e,o.sub),A={machine:a,envelope:d,signal:o.signal,deadlineAt:o.deadlineAt,mapFailure:D};if(V(r.major,s.tool)){const n=await $e(A,t.arguments,r.major),b=I(c.output,n);if(b)throw new p("provider_bad_response",`the libraries result has an invalid output field "${b.field}"`,{origin:"provider",param:b.field,details:{status:200,...b}});return{result:n,responder:void 0,cleanup:void 0}}let m=t.arguments,h=null,j="";if(Ee(r.major,s.tool,t.arguments)){const n=await Ce(A,t.arguments,r.major);if(o.deadlineAt-Date.now()<=0)throw Q(a,"timeout","");n!==null&&(m=n.arguments,h=n.services,j=n.release)}const u=await ke(a,{tool:s.tool,arguments:m,version:r.major,requestId:o.requestId,runId:e.run_id,deadlineAt:o.deadlineAt,upload:t.upload===null?null:{id:t.upload.upload_id,link:t.upload.upload_link}},d,o.signal);if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");if("unreachable"in u)throw Q(a,u.unreachable.reason,u.unreachable.message);const _=u.headers["crtr-responder"],g=B(u.body);if(u.status<200||u.status>=300){const n=Pe(a,u.status,typeof g.code=="string"?g.code:void 0,typeof g.message=="string"?g.message:void 0,u.body),b=n===null&&u.status>=500&&typeof g.code=="string"?new p("provider_error",typeof g.message=="string"?g.message:`the device answered HTTP ${u.status}`,{origin:"runtime",status:u.status,details:{code:g.code,status:u.status}}):null;throw Object.assign(n??b??D(u.status,u.body,u.headers),{responder:_})}const w=u.body&&typeof u.body=="object"&&!Array.isArray(u.body)?u.body.result:void 0;if(!w||typeof w!="object"||Array.isArray(w))throw Object.assign(new p("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:u.status,message:"the device result is not a JSON object"}}),{responder:_});let P=w;if(h!==null){const n=Re(P,h);if(Se(a,d,j,n.calls),"error"in n)throw Object.assign(n.error,{responder:_});P=n.result}let i;try{i=await z(e,t,P,{signal:o.signal})}catch(n){throw n instanceof p&&n.code==="provider_error"?Object.assign(new p("provider_bad_response",n.message,{origin:"provider",details:{status:u.status,message:n.message}}),{responder:_}):n}const k=I(c.output,i.result);if(k)throw await i.cleanup?.(),Object.assign(new p("provider_bad_response",`the device result has an invalid output field "${k.field}"`,{origin:"provider",param:k.field,details:{status:u.status,...k}}),{responder:_});return{result:i.result,responder:_,cleanup:i.cleanup}}v(Je,"callDevice");export{vr as callProvider,Le as crtrCallerHeader,D as mapProviderResponse,Me as onProviderResponse,W as providerLeaves,mr as providerTools};
1
+ var te=Object.defineProperty;var v=(e,r)=>te(e,"name",{value:r,configurable:!0});import{validateDeclaredResult as U}from"../../api/command-manifest/result.js";import{callerAllows as oe,requireGroup as ie}from"../../daemon/api/handlers/providers.js";import{terminalGrant as ne}from"../grants/mirror.js";import{UNCAPPED_GRANTEE as se}from"../usage/ledger.js";import{listInstalledPlugins as ae}from"../resolver.js";import{ProviderError as l}from"./errors.js";import{resolveProviderRef as de}from"./roots.js";import{providerToolPath as ue}from"../plugin-objects-manifest.js";import{ensureProviderBundle as le,refreshProviderBundle as pe,resolveProviderBundle as B,unavailableProviderError as ce}from"./bundles.js";import{prepareFileCall as fe,resignForRetry as z,resolveFileResult as Q}from"./files.js";import{consumeApproval as me,requireProviderApproval as ve}from"./approval.js";import{outboundToken as ge}from"./mint.js";import{discardFile as he}from"./token-cache.js";import{registerProviderCall as we}from"./inflight.js";import{buildRequestPlan as be}from"../command-plugins/transport/http-plan.js";import{newTraceId as ye}from"../../shared/traceparent.js";import{sendProviderRequest as _e,ProviderTransportError as Ae}from"./request.js";import{acceptProviderReceipt as je}from"./receipts.js";import{writeProviderAudit as ke}from"./audit.js";import{selectDevice as Pe,sendDeviceCall as qe,deviceFailure as Ee,deviceUnreachable as V,DEFAULT_DEVICE_REQUEST_MS as $e}from"./device.js";import{isLibsSearch as K,wantsBundling as Ce,callLibsSearch as Re,bundleExec as Ie,mapLibOutcome as Se,reportLibCalls as Te}from"./device-libs.js";import{directoryIssuer as Ne,ownMachineId as De}from"../peer/directory.js";import{formatCrtrCaller as W}from"../command-plugins/caller-header.js";import{getNode as Fe}from"../canvas/canvas.js";import{envelopeForProviderCaller as Me}from"../peer/envelope.js";function X(e){const r=[],n=v((c,t,o)=>{const s=[...t,c.name],a=o||c.tier==="hidden";if(c.kind==="branch"){for(const g of c.children)n(g,s,a);return}const j=ue(e.roots.length,s);r.push({tool:j,leaf:c,hidden:a})},"walk");for(const c of e.roots)n(c,[],!1);return r}v(X,"providerLeaves");function Y(e){if(e.kind!=="branch")return null;const r=[];for(const h of e.children)if(h.kind==="branch"){const _=Y(h);_&&r.push(_)}else if("group"in h&&h.group){const{kind:_,name:d,description:A,whenToUse:w,tier:b,summary:k,params:P,output:i,effects:p,group:m}=h;r.push({kind:_,name:d,description:A,whenToUse:w,...b===void 0?{}:{tier:b},summary:k,params:P,output:i,effects:p,group:m})}if(r.length===0)return null;const{kind:n,name:c,description:t,whenToUse:o,tier:s,summary:a,model:j,rootEntry:g}=e;return{kind:n,name:c,description:t,whenToUse:o,...s===void 0?{}:{tier:s},summary:a,...j===void 0?{}:{model:j},...g===void 0?{}:{rootEntry:g},children:r}}v(Y,"providerHelpBranch");async function hr(e,r){const n=await B(r),c=X(n.commands),t=ae("user").find(o=>o.name===r)?.manifest.description??r;return{provider:r,status:"available",major:n.major,display_name:t,transport:n.transport.kind,groups:n.provider.groups.filter(o=>oe(e,`${r}:${o.group}`)).map(o=>({group:o.group,leaves:c.filter(({leaf:s,hidden:a})=>!a&&s.group===o.group).map(({tool:s,leaf:a})=>({tool:s,description:a.description,summary:a.summary,params:a.params,output:a.output}))})),commands:n.commands.roots.flatMap(o=>{const s=Y(o);return s?[s]:[]})}}v(hr,"providerTools");async function Oe(e){return await je(e.headers["crtr-receipt"],e)}v(Oe,"onProviderResponse");async function Ue(e){if(e.class==="person"){const r=(await ne())?.sub;if(!r)throw new l("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 l("grant_revoked","the caller has no active grant");return{sub:e.sub,grantee:e.grantee}}v(Ue,"identity");function Be(e,r){return[`${e.id}:${r.group}`]}v(Be,"callScope");function Le(e,r){const n=X(e.commands).find(c=>c.tool===r);if(!n)throw new l("not_found",`tool "${r}" is not in provider "${e.id}"`,{param:"tool"});if(!n.leaf.group)throw new l("not_found",`tool "${r}" has no group`,{param:"tool"});return n.leaf}v(Le,"leafFor");function L(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r=e.error;return!r||typeof r!="object"||Array.isArray(r)?{}:r}v(L,"errorBody");function Je(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 c=Date.parse(r);return Number.isFinite(c)?Math.max(0,Math.ceil((c-Date.now())/1e3)):void 0}v(Je,"retryAfter");function N(e,r,n,c=!1){const t=L(r),o=typeof t.code=="string"?t.code:void 0,s=typeof t.message=="string"?t.message:`provider answered HTTP ${e}`,a=typeof t.field=="string"?t.field:void 0,g={origin:"provider",details:{code:o??null,message:s,...a?{field:a}:{},...typeof t.next=="string"?{next:t.next}:{},...t.received!==void 0?{received:t.received}:{},status:e},...a?{param:a}:{}};return e===429?new l("provider_rate_limited",s,{...g,retry_after_s:Je(n["retry-after"])}):e===401||e>=500?new l("provider_unavailable",s,g):e===403&&o==="provider_refused"?new l("provider_refused",s,g):e===403&&o==="approval_required"?new l("provider_error",c?"provider asked for approval again":"provider approval question is invalid",{...g,status:e}):new l("provider_error",s,{...g,status:e})}v(N,"mapProviderResponse");function Z(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})}v(Z,"approvalShape");async function He(e){const r=e.origin!==void 0?W({node:e.origin.node??void 0,run:e.origin.run??void 0,machine:e.origin.machine}):W({node:e.node_id??void 0,run:e.run_id??void 0,profile:e.node_id?(await Fe(e.node_id))?.profile_id??void 0:void 0,machine:Ne()!==null?De():void 0});return r===""?{}:{"Crtr-Caller":r}}v(He,"crtrCallerHeader");async function wr(e,r,n){const c=de(r.provider);if(c===null)throw new l("not_found",`provider "${r.provider}" is not available`,{param:"provider"});const t={...r,provider:c},o=new AbortController,s=v(()=>o.abort(n.signal.reason),"abort");n.signal.aborted?s():n.signal.addEventListener("abort",s,{once:!0});let a=n.requestId;const j=n.traceId??ye(),g=we({request_id:a,run_id:e.run_id,node_id:e.node_id,controller:o});let h,_,d,A,w,b,k,P="allowed";try{try{await le(t.provider)}catch{throw ce(t.provider)}let i=await B(t.provider,t.version),p=!1,m,D,q,J=!1,H=!1,F,x=0;for(;;){const y=Le(i,t.tool);if(d=`${i.id}:${y.group}`,ie(e,i.id,y.group),i.transport.kind!=="machine"&&t.machine!==void 0)throw new l("invalid_argument",`provider "${i.id}" is not served by a device, so it takes no machine`,{param:"machine"});i.transport.kind==="machine"&&F===void 0&&(F=await Pe(t.machine,void 0,{newest:K(i.major,t.tool)}),x=Date.now()+(i.commands.timeouts?.requestMs??$e));let M=null;if(t.approval&&!J){const u=await me(e,t,Z(i,y));q=u.option,M=u.prepared,k=t.approval,a=u.requestId,J=!0}const C={params:y.params.filter(u=>"type"in u&&u.type==="file").map(u=>u.name),output:y.output.find(u=>u.type==="file")?.name??null},G=JSON.stringify(C);if(D!==void 0&&D!==G)throw new l("provider_bad_response","the refreshed manifest changed the tool file contract",{origin:"provider",details:{status:409,message:"manifest_stale changed the tool file contract"}});D=G,M&&(m=await z(e,C,t.arguments,M,{signal:o.signal})),m??=await fe(e,C,t.arguments,{signal:o.signal});const{sub:O,grantee:R}=await Ue(e);if(i.transport.kind==="machine"){const u=await xe(e,i,t,y,m,{sub:O,requestId:a,deadlineAt:x,signal:o.signal,machine:F});return b=u.responder,h=u.cleanup,{status:200,body:u.result}}const E=await ge({audience:i.transport.endpoint,sub:O,grantee:R,initiating_app:R,scope:Be(i,y)},{signal:o.signal});A=E.jti;const ee={registration:{name:i.id,endpoint:i.transport.endpoint},...i.commands.baseUrl?{baseUrl:i.commands.baseUrl}:{},...i.commands.timeouts?{timeouts:i.commands.timeouts}:{},params:y.params,rest:y.rest,output:y.output,commandPath:[i.id,...t.tool.split(" ")]},re=be(ee,m.arguments,void 0,{authorization:`Bearer ${E.access_token}`,requestId:a,traceId:j,fileMode:"pass-through",extraHeaders:{...e.run_id?{"Crtr-Run-Id":e.run_id}:{},...await He(e),...q?{"Crtr-Approval":q}:{},...m.headers}});_??=Date.now()+(i.commands.timeouts?.requestMs??3e4);let f;try{if(f=await _e(re,{connectMs:i.commands.timeouts?.connectMs,requestMs:i.commands.timeouts?.requestMs,deadlineAt:_,signal:o.signal}),o.signal.aborted)throw o.signal.reason??new Error("provider call aborted")}catch(u){throw o.signal.aborted?o.signal.reason??u:u instanceof Ae?new l("provider_unavailable",u.message,{origin:"provider",details:{status:null,code:u.kind}}):u}if(w??=await Oe({provider:i.id,tool:t.tool,request_id:a,run_id:e.run_id,sub:O,grantee:R,payer:E.payer,ledger_grantee:e.class==="person"?se:R,jti:E.jti,status:f.status,headers:f.headers}),b=f.headers["crtr-responder"],f.status===401&&he(E.key),f.status>=200&&f.status<300){const u=f.body;if(!u||typeof u!="object"||Array.isArray(u))throw new l("provider_bad_response","provider result is not a JSON object",{origin:"provider",details:{status:f.status,message:"provider result is not a JSON object"}});let S;try{S=await Q(e,m,u,{signal:o.signal})}catch($){throw $ instanceof l&&$.code==="provider_error"?new l("provider_bad_response",$.message,{origin:"provider",details:{status:f.status,message:$.message}}):$}h=S.cleanup;const T=U(y.output,S.result);if(T)throw new l("provider_bad_response",`provider result has an invalid output field "${T.field}"`,{origin:"provider",param:T.field,details:{status:f.status,...T}});if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");return{status:200,body:S.result}}const I=L(f.body);if(f.status===403&&I.code==="approval_required"&&!q&&!H){H=!0;const u=await ve(e,t,a,I.question,o.signal,Z(i,y),m);if(k=u.questionId,q=u.option,m=await z(e,C,t.arguments,m,{signal:o.signal}),_=void 0,o.signal.aborted)throw new l("user_denied","the approval wait was canceled");continue}if(f.status===403&&I.code==="approval_required"&&q)throw N(f.status,f.body,f.headers,!0);if(f.status!==401&&I.manifest_stale===!0&&!p){p=!0,await pe(i.id,{conditional:!1,...t.version===void 0?{}:{version:t.version}}),i=await B(i.id,t.version);continue}throw N(f.status,f.body,f.headers,!!q)}}catch(i){throw i instanceof l&&i.question_id&&(k=i.question_id),b===void 0&&i instanceof l&&typeof i.responder=="string"&&(b=i.responder),P=`refused:${i instanceof l?i.code:"server_error"}`,await h?.(),i}finally{try{await ke({request_id:a,caller:e,provider:t.provider,tool:t.tool,scope_checked:d,outcome:P,jti:A,receipt_id:w,responder:b,question_id:k})}finally{g()}n.signal.removeEventListener("abort",s)}}v(wr,"callProvider");async function xe(e,r,n,c,t,o){const{machine:s}=o,a=await Me(e,o.sub),j={machine:s,envelope:a,signal:o.signal,deadlineAt:o.deadlineAt,mapFailure:N};if(K(r.major,n.tool)){const p=await Re(j,t.arguments,r.major),m=U(c.output,p);if(m)throw new l("provider_bad_response",`the libraries result has an invalid output field "${m.field}"`,{origin:"provider",param:m.field,details:{status:200,...m}});return{result:p,responder:void 0,cleanup:void 0}}let g=t.arguments,h=null,_="";if(Ce(r.major,n.tool,t.arguments)){const p=await Ie(j,t.arguments,r.major);if(o.deadlineAt-Date.now()<=0)throw V(s,"timeout","");p!==null&&(g=p.arguments,h=p.services,_=p.release)}const d=await qe(s,{tool:n.tool,arguments:g,version:r.major,requestId:o.requestId,runId:e.run_id,deadlineAt:o.deadlineAt,upload:t.upload===null?null:{id:t.upload.upload_id,link:t.upload.upload_link}},a,o.signal);if(o.signal.aborted)throw o.signal.reason??new Error("provider call aborted");if("unreachable"in d)throw V(s,d.unreachable.reason,d.unreachable.message);const A=d.headers["crtr-responder"],w=L(d.body);if(d.status<200||d.status>=300){const p=Ee(s,d.status,typeof w.code=="string"?w.code:void 0,typeof w.message=="string"?w.message:void 0,d.body),m=p===null&&d.status>=500&&typeof w.code=="string"?new l("provider_error",typeof w.message=="string"?w.message:`the device answered HTTP ${d.status}`,{origin:"runtime",status:d.status,details:{code:w.code,status:d.status}}):null;throw Object.assign(p??m??N(d.status,d.body,d.headers),{responder:A})}const b=d.body&&typeof d.body=="object"&&!Array.isArray(d.body)?d.body.result:void 0;if(!b||typeof b!="object"||Array.isArray(b))throw Object.assign(new l("provider_bad_response","the device result is not a JSON object",{origin:"provider",details:{status:d.status,message:"the device result is not a JSON object"}}),{responder:A});let k=b;if(h!==null){const p=Se(k,h);if(Te(s,a,_,p.calls),"error"in p)throw Object.assign(p.error,{responder:A});k=p.result}let P;try{P=await Q(e,t,k,{signal:o.signal})}catch(p){throw p instanceof l&&p.code==="provider_error"?Object.assign(new l("provider_bad_response",p.message,{origin:"provider",details:{status:d.status,message:p.message}}),{responder:A}):p}const i=U(c.output,P.result);if(i)throw await P.cleanup?.(),Object.assign(new l("provider_bad_response",`the device result has an invalid output field "${i.field}"`,{origin:"provider",param:i.field,details:{status:d.status,...i}}),{responder:A});return{result:P.result,responder:A,cleanup:P.cleanup}}v(xe,"callDevice");export{wr as callProvider,He as crtrCallerHeader,N as mapProviderResponse,Oe as onProviderResponse,X as providerLeaves,hr as providerTools};
@@ -12,7 +12,7 @@ export interface RunStreamOptions {
12
12
  idleCheckMs?: number;
13
13
  isRunning?: (runId: string) => Promise<boolean>;
14
14
  }
15
- export declare function streamRunEvents(req: IncomingMessage, res: ServerResponse, id: string, principal: AppPrincipal | RequestPrincipal, after: number, requestId: string, framing: 'sse' | 'ndjson', options?: RunStreamOptions): Promise<void>;
15
+ export declare function streamRunEvents(req: IncomingMessage, res: ServerResponse, id: string, principal: AppPrincipal | RequestPrincipal, after: number, requestId: string, framing: 'sse' | 'ndjson', options?: RunStreamOptions, traceId?: string): Promise<void>;
16
16
  export declare function createAppListener(config: NonNullable<ScopeConfig['runtime']>, grantsReady: () => Promise<boolean>, syncGrants: GrantSync, listenerOptions?: {
17
17
  deferHead?: DeferHeadOptions;
18
18
  stream?: RunStreamOptions;
@@ -1,24 +1,24 @@
1
- var F=Object.defineProperty;var i=(e,r)=>F(e,"name",{value:r,configurable:!0});import{randomUUID as Y}from"node:crypto";import{createServer as Q}from"node:http";import{Router as L}from"./router.js";import{toErrorBody as W}from"./map.js";import{objectRoutes as q}from"./handlers/objects.js";import{customObjectRoutes as K}from"./handlers/custom-objects.js";import{docRoutes as Z}from"./handlers/docs.js";import{OPERATIONS as ee}from"./operations.js";import{runtimeReadiness as te}from"../readiness.js";import{errorCodes as U}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as ne,RUNTIME_VERSION_HEADER as re,minimumRuntimeRefusal as ae}from"../../api/runtime-version.js";import{CONTRACT_HEADER as oe,CONTRACT_LEVEL as V,REQUIRED_CONTRACT_HEADER as ie,contractRefusal as se}from"../../api/contract.js";import{crtrVersion as j}from"../../shared/crtr-version.js";import{AppPrincipalResolver as ue}from"./principal.js";import{isDaemonRoute as ce,runtimeOwner as de,servePeer as X}from"../peer/ingress.js";import{applySyncNotice as le}from"./sync-notice.js";import{ownMachineId as me}from"../../core/peer/directory.js";import{handleRunOperation as fe,integer as pe}from"./handlers/runs.js";import{llmCallerOf as he,parseLlmRequest as we,runLlm as ye}from"../../core/llm/run.js";import{parseUsageMonth as _e,usageFor as ve}from"../../core/usage/ledger.js";import{capFields as ge}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as Re,MODEL_CONTEXT_BODY_MAX_BYTES as Oe}from"../../core/llm/body-limits.js";import{RunError as m,runIsRunning as Ee}from"../../core/runs/operations.js";import{requireRun as x}from"./router.js";import{deferHead as be}from"./deferred-head.js";import{readRunEvents as z,registerGrantStream as Se,subscribeRunEvents as Te}from"../../core/runs/events.js";import{activeGrant as $,payerOfGrant as Ae}from"../../core/grants/mirror.js";async function Ne(e){if(await Ae(e)==="app")throw new m(401,"grant_revoked","this app-paid grant is no longer active");return null}i(Ne,"appPaidGone");import{releaseGrantWaiters as Ce}from"../../core/grants/watch.js";import{emitEvent as Ie}from"../../core/events/emit.js";import{runNotices as Pe}from"../run-notices.js";import{handleVendorSignIn as ke,isVendorSignInPath as He}from"./vendor-signin.js";import{listAppProfiles as Me}from"./handlers/app-profiles.js";import{handleTransferDownload as De,handleTransferOperation as je,TRANSFER_OPERATIONS as xe}from"./handlers/transfer.js";import{handleQuestionOperation as Be,QUESTION_OPERATIONS as Je}from"./handlers/questions.js";import{removeVendorAccount as Le,vendorStatus as Ue}from"./handlers/vendor-status.js";import{handleMemoryOperation as Ve,MEMORY_OPERATIONS as Xe}from"./handlers/memory.js";import{abortOnClose as ze,callerFromApp as $e,handleProviderOperation as Ge,PROVIDER_OPERATIONS as Fe,providerFailure as Ye}from"./handlers/providers.js";function Qe(e){return Object.prototype.hasOwnProperty.call(U,e)?U[e]:void 0}i(Qe,"fixedRow");function We(e){const r=Qe(e);return r?r.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}i(We,"errorType");function qe(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:r,...o}=e;return o}i(qe,"appDetails");function D(e,r,o){const f=qe(e.details);return{error:{code:e.code,message:e.message,...f===void 0?{}:{details:f},type:We(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:r,...o?{run_id:o}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...ge(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}i(D,"envelope");function Ke(e,r,o,f){const u=r instanceof m?r:new m(500,"server_failure","runtime request failed");if(r instanceof m||console.error("[app-listener]",r),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
2
- data: ${JSON.stringify(D(u,o,f))}
1
+ var Y=Object.defineProperty;var i=(e,r)=>Y(e,"name",{value:r,configurable:!0});import{randomUUID as Q}from"node:crypto";import{createServer as W}from"node:http";import{Router as V}from"./router.js";import{toErrorBody as q}from"./map.js";import{objectRoutes as K}from"./handlers/objects.js";import{customObjectRoutes as Z}from"./handlers/custom-objects.js";import{docRoutes as ee}from"./handlers/docs.js";import{OPERATIONS as te}from"./operations.js";import{runtimeReadiness as ne}from"../readiness.js";import{errorCodes as $}from"../../api/error-codes.js";import{MINIMUM_RUNTIME_VERSION_HEADER as re,RUNTIME_VERSION_HEADER as ae,minimumRuntimeRefusal as oe}from"../../api/runtime-version.js";import{CONTRACT_HEADER as ie,CONTRACT_LEVEL as X,REQUIRED_CONTRACT_HEADER as se,contractRefusal as ue}from"../../api/contract.js";import{crtrVersion as B}from"../../shared/crtr-version.js";import{newTraceId as ce,parseTraceparent as de}from"../../shared/traceparent.js";import{AppPrincipalResolver as le}from"./principal.js";import{isDaemonRoute as me,runtimeOwner as fe,servePeer as z}from"../peer/ingress.js";import{applySyncNotice as pe}from"./sync-notice.js";import{ownMachineId as he}from"../../core/peer/directory.js";import{handleRunOperation as we,integer as ye}from"./handlers/runs.js";import{llmCallerOf as _e,parseLlmRequest as ve,runLlm as ge}from"../../core/llm/run.js";import{parseUsageMonth as Re,usageFor as Oe}from"../../core/usage/ledger.js";import{capFields as Ee}from"./handlers/llm.js";import{CONTROL_BODY_MAX_BYTES as be,MODEL_CONTEXT_BODY_MAX_BYTES as Te}from"../../core/llm/body-limits.js";import{RunError as m,runIsRunning as Se}from"../../core/runs/operations.js";import{requireRun as J}from"./router.js";import{deferHead as Ae}from"./deferred-head.js";import{readRunEvents as G,registerGrantStream as Ne,subscribeRunEvents as Ce}from"../../core/runs/events.js";import{activeGrant as F,payerOfGrant as Ie}from"../../core/grants/mirror.js";async function Pe(e){if(await Ie(e)==="app")throw new m(401,"grant_revoked","this app-paid grant is no longer active");return null}i(Pe,"appPaidGone");import{releaseGrantWaiters as ke}from"../../core/grants/watch.js";import{emitEvent as He}from"../../core/events/emit.js";import{runNotices as Me}from"../run-notices.js";import{handleVendorSignIn as De,isVendorSignInPath as je}from"./vendor-signin.js";import{listAppProfiles as xe}from"./handlers/app-profiles.js";import{handleTransferDownload as Be,handleTransferOperation as Je,TRANSFER_OPERATIONS as Le}from"./handlers/transfer.js";import{handleQuestionOperation as Ue,QUESTION_OPERATIONS as Ve}from"./handlers/questions.js";import{removeVendorAccount as $e,vendorStatus as Xe}from"./handlers/vendor-status.js";import{handleMemoryOperation as ze,MEMORY_OPERATIONS as Ge}from"./handlers/memory.js";import{abortOnClose as Fe,callerFromApp as Ye,handleProviderOperation as Qe,PROVIDER_OPERATIONS as We,providerFailure as qe}from"./handlers/providers.js";function Ke(e){return Object.prototype.hasOwnProperty.call($,e)?$[e]:void 0}i(Ke,"fixedRow");function Ze(e){const r=Ke(e);return r?r.type:e==="stream_gap"||e==="provider_error"||e==="runtime_version_unsupported"?"invalid_request_error":"server_error"}i(Ze,"errorType");function et(e){if(!e||typeof e!="object"||Array.isArray(e)||!("rate_limit"in e))return e;const{rate_limit:r,...o}=e;return o}i(et,"appDetails");function x(e,r,o){const p=et(e.details);return{error:{code:e.code,message:e.message,...p===void 0?{}:{details:p},type:Ze(e.code),...e.param?{param:e.param}:{},origin:"runtime",retryable:e.retryable||e.code==="stream_dropped",request_id:r,...o?{run_id:o}:{},...e.scope?{scope:e.scope}:{},...e.code==="unauthorized"?{user_action:"reconnect"}:{},...Ee(e),...e.code==="all_routes_exhausted"?{retry_after_s:e.details.retry_after_s}:{}}}}i(x,"envelope");function tt(e,r,o,p,f){const l=r instanceof m?r:new m(500,"server_failure","runtime request failed");if(r instanceof m||console.error("[app-listener]",`trace_id=${p}`,r),e.headersSent&&!String(e.getHeader("Content-Type")??"").startsWith("text/event-stream")){e.destroy();return}if(e.headersSent){e.end(`event: error
2
+ data: ${JSON.stringify(x(l,o,f))}
3
3
 
4
- `);return}e.writeHead(u.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":o,"X-Content-Type-Options":"nosniff",...u.code==="all_routes_exhausted"?{"Retry-After":String(u.details.retry_after_s)}:{}}),e.end(JSON.stringify(D(u,o,f)))}i(Ke,"fail");function G(e){return Array.isArray(e)?e[0]:e}i(G,"headerValue");function R(e,r,o){e.writeHead(r,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}i(R,"json");async function N(e,r=Re){const o=[];let f=0;for await(const u of e){if(f+=u.length,f>r)throw new m(400,"invalid_request","request body too large");o.push(u)}if(f)try{return JSON.parse(Buffer.concat(o).toString("utf8"))}catch{throw new m(400,"invalid_request","invalid JSON body")}}i(N,"bodyOf");function Ze(e,r){const o=e.headers["last-event-id"]??r.get("after");if(Array.isArray(o))throw new m(400,"invalid_request","invalid cursor","after");return pe(o??null,"after",0,Number.MAX_SAFE_INTEGER,0)}i(Ze,"cursor");function et(e){return`id: ${e.sequence_number}
4
+ `);return}e.writeHead(l.status,{"Content-Type":"application/json; charset=utf-8","Request-Id":o,"X-Content-Type-Options":"nosniff",...l.code==="all_routes_exhausted"?{"Retry-After":String(l.details.retry_after_s)}:{}}),e.end(JSON.stringify(x(l,o,f)))}i(tt,"fail");function L(e){return Array.isArray(e)?e[0]:e}i(L,"headerValue");function g(e,r,o){e.writeHead(r,{"Content-Type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}i(g,"json");async function C(e,r=be){const o=[];let p=0;for await(const f of e){if(p+=f.length,p>r)throw new m(400,"invalid_request","request body too large");o.push(f)}if(p)try{return JSON.parse(Buffer.concat(o).toString("utf8"))}catch{throw new m(400,"invalid_request","invalid JSON body")}}i(C,"bodyOf");function nt(e,r){const o=e.headers["last-event-id"]??r.get("after");if(Array.isArray(o))throw new m(400,"invalid_request","invalid cursor","after");return ye(o??null,"after",0,Number.MAX_SAFE_INTEGER,0)}i(nt,"cursor");function rt(e){return`id: ${e.sequence_number}
5
5
  event: ${e.type}
6
6
  data: ${JSON.stringify(e)}
7
7
 
8
- `}i(et,"frame");const tt=6e4;async function nt(e,r,o,f,u,w,b,C={}){await x(f,o);let v=await z(o,u);if(u>v.latest_sequence)throw new m(400,"invalid_request","cursor is beyond the run","after");if(u<v.latest_sequence&&(v.earliest_sequence===null||u<v.earliest_sequence-1))throw new m(409,"stream_gap","events have expired",void 0,{earliest_sequence:v.earliest_sequence,latest_sequence:v.latest_sequence});if(r.destroyed||e.destroyed)return r.destroy(),Promise.resolve();r.writeHead(200,{"Content-Type":b==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),b==="sse"?r.write(`: connected
8
+ `}i(rt,"frame");const at=6e4;async function ot(e,r,o,p,f,l,b,I={},H){await J(p,o);let w=await G(o,f);if(f>w.latest_sequence)throw new m(400,"invalid_request","cursor is beyond the run","after");if(f<w.latest_sequence&&(w.earliest_sequence===null||f<w.earliest_sequence-1))throw new m(409,"stream_gap","events have expired",void 0,{earliest_sequence:w.earliest_sequence,latest_sequence:w.latest_sequence});if(r.destroyed||e.destroyed)return r.destroy(),Promise.resolve();r.writeHead(200,{"Content-Type":b==="sse"?"text/event-stream":"application/x-ndjson","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),b==="sse"?r.write(`: connected
9
9
 
10
- `):r.flushHeaders();let y=u;const B=await Pe().streamOpened(o);let n=!1,t=!1,h=!1,S;const I=new Promise(c=>{S=c}),_=i(()=>{n||(n=!0,B(y).catch(c=>console.error("[app-listener]",c)),clearInterval(d),clearInterval(A),P(),H(),r.off("close",_),r.off("drain",p),r.end(),S())},"end"),p=i(()=>{h=!1,a()},"onDrain"),O=i(c=>{n||r.write(b==="sse"?`event: error
11
- data: ${JSON.stringify(D(c,w,o))}
10
+ `):r.flushHeaders();let S=f;const n=await Me().streamOpened(o);let t=!1,y=!1,O=!1,T;const P=new Promise(u=>{T=u}),_=i(()=>{t||(t=!0,n(S).catch(u=>console.error("[app-listener]",...H===void 0?[]:[`trace_id=${H}`],u)),clearInterval(c),clearInterval(N),k(),D(),r.off("close",_),r.off("drain",h),r.end(),T())},"end"),h=i(()=>{O=!1,a()},"onDrain"),R=i(u=>{t||r.write(b==="sse"?`event: error
11
+ data: ${JSON.stringify(x(u,l,o))}
12
12
 
13
- `:`${JSON.stringify({type:"error",...D(c,w,o)})}
14
- `),_()},"error"),a=i(async()=>{if(!(n||t)){t=!0;try{if(!await T()){k();return}if(await x(f,o),v=await z(o,y),v.backlog_count>1e3||v.backlog_bytes>32*1024*1024){O(new m(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:y}));return}for(const c of v.events){if(h)break;h=!r.write(b==="sse"?et(c):`${JSON.stringify(c)}
15
- `),y=c.sequence_number}}catch(c){O(c instanceof m?c:new m(500,"server_failure","stream failed"))}finally{t=!1}}},"flush"),k=i(()=>O(new m(401,"grant_revoked","grant is not active")),"revoked"),T=i(async()=>"class"in f||await $(f.grant)!==null,"grantActive"),P=await Te(o,a),H="class"in f?()=>{}:Se(f.grant,k),d=setInterval(()=>{!n&&!h&&b==="sse"&&r.write(`: keepalive
13
+ `:`${JSON.stringify({type:"error",...x(u,l,o)})}
14
+ `),_()},"error"),a=i(async()=>{if(!(t||y)){y=!0;try{if(!await A()){M();return}if(await J(p,o),w=await G(o,S),w.backlog_count>1e3||w.backlog_bytes>32*1024*1024){R(new m(503,"stream_dropped","stream reader fell behind",void 0,{last_sequence:S}));return}for(const u of w.events){if(O)break;O=!r.write(b==="sse"?rt(u):`${JSON.stringify(u)}
15
+ `),S=u.sequence_number}}catch(u){R(u instanceof m?u:new m(500,"server_failure","stream failed"))}finally{y=!1}}},"flush"),M=i(()=>R(new m(401,"grant_revoked","grant is not active")),"revoked"),A=i(async()=>"class"in p||await F(p.grant)!==null,"grantActive"),k=await Ce(o,a),D="class"in p?()=>{}:Ne(p.grant,M),c=setInterval(()=>{!t&&!O&&b==="sse"&&r.write(`: keepalive
16
16
 
17
- `)},15e3),M=C.idleMs??tt,s=C.isRunning??Ee;let g=Date.now(),l=y;const E=i(async()=>{if(n||b!=="sse")return;const c=Date.now();let J=!0;try{J=await s(o)}catch{}if(!n){if(J||y!==l){g=c,l=y;return}c-g<M||h||(r.write(`event: idle
17
+ `)},15e3),j=I.idleMs??at,s=I.isRunning??Se;let v=Date.now(),d=S;const E=i(async()=>{if(t||b!=="sse")return;const u=Date.now();let U=!0;try{U=await s(o)}catch{}if(!t){if(U||S!==d){v=u,d=S;return}u-v<j||O||(r.write(`event: idle
18
18
  data: {}
19
19
 
20
- `),_())}},"checkIdle"),A=setInterval(()=>{E().catch(c=>Ie({level:"error",event:"api.run_stream.idle_check_failed",error:c}))},C.idleCheckMs??1e3);return A.unref(),r.on("drain",p),r.on("close",_),await a(),I}i(nt,"streamRunEvents");async function Ut(e,r,o,f={}){const u=e.listener;if(!e.issuer||!e.audience||!u?.address||!Number.isInteger(u.port)||u.port<1||u.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(u.address))throw new Error("runtime listener must bind a specific address");const w=new ue(e.issuer,e.audience,o,e.jwksUrl),b=new L,C=new Map;for(const n of ee.filter(t=>t.stage!==void 0)){const t=i(()=>({status:500}),"handler");b.register({method:n.method,pattern:n.path,handler:t}),C.set(t,n)}const v=new L().registerAll(q).registerAll(K).registerAll(Z),y=Q((n,t)=>{(async()=>{be(n,t,f.deferHead);const h=Y();t.setHeader("Request-Id",h),t.setHeader(re,j()),t.setHeader(oe,V);let S;try{const I=G(n.headers[ne.toLowerCase()]),_=ae(I,j())??se(G(n.headers[ie.toLowerCase()]),V,{legacyMinimum:I,runtimeVersion:j()});if(_!==null)throw _.status===400?new m(400,_.code,_.message,_.param):new m(426,_.code,_.message,void 0,_.details);const p=new URL(n.url??"/","http://runtime.local");if(He(p.pathname)){await ke(n,t,p,w);return}const O=b.match(n.method??"GET",p.pathname),a=O?C.get(O.handler):void 0,k=a?.name==="runtime.sync-notice"||a?.name==="runtime.vendor-status"||a?.name==="runtime.vendor-account-remove";let T,P;if(!k&&n.headers.authorization!==void 0)try{T=await w.verifyCall(n.headers.authorization)}catch(s){P=s}const H={issuer:e.issuer,jwks:w.jwks,audience:i(async()=>e.audience,"audience"),owner:de,self:me,syncGrants:o,ready:i(async()=>w.isReady()&&await r(),"ready")};if(T?.chain[0]?.sub.startsWith("runtime:")){await X(n,t,H,T);return}if(T===void 0&&a===void 0&&await ce(n.method??"GET",p.pathname)){await X(n,t,H,{status:401,code:"unauthorized",message:P===void 0?"a machine token is required":"invalid machine token"});return}if(a?.name==="ready"){const s=await r(),g=await te({jwksReady:w.isReady(),grantsReady:i(()=>s,"grantsReady")});R(t,g.ready?200:503,g);return}if((n.method!=="GET"||p.pathname!=="/v1/health")&&(!w.isReady()||!await r()))throw new m(503,"runtime_starting","runtime_starting");if(!O||!a)throw new m(404,"not_found","unknown operation");if(S=O.params.run_id,a.name==="health"){R(t,200,{status:"ok",api_version:"2026-09-23"});return}if(a.name==="runtime.sync-notice"){await le(await w.verifySyncNotice(n.headers.authorization),o,h),t.writeHead(204),t.end();return}if(a.name==="runtime.vendor-status"){await w.verifyDirectoryRead(n.headers.authorization,"vendor_status"),R(t,200,Ue());return}if(a.name==="runtime.vendor-account-remove"){await w.verifyDirectoryRead(n.headers.authorization,"vendor_account_remove"),R(t,200,await Le(await N(n)));return}if(T===void 0)throw P??new m(401,"unauthorized","a directory access token is required");const d=await w.resolveVerified(T,a.scope);if(t.setHeader("Request-Id",h),S&&await x(d,S,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(a.name),a.name==="run.delete"),a.stage==="objects"){const s=v.match(a.method,p.pathname);if(!s)throw new m(404,"not_found","unknown operation");const g={method:a.method,path:p.pathname,params:s.params,query:p.searchParams,body:await N(n,s.maxBodyBytes),ceiling:d.resolved,principal:{class:"app",app:d.grantee,scopes:d.resolved},req:n,res:t};let l;try{l=await s.handler(g)}catch(E){const{status:A,body:c}=W(E);throw A>=500&&console.error("[app-listener]",E),new m(A,c.error.code,c.error.message,void 0,c.error.details)}if(t.headersSent)return;if(l.rawBody!==void 0||l.status===204||l.body===void 0){t.writeHead(l.status,l.headers??{}),t.end(l.rawBody);return}t.writeHead(l.status,{"Content-Type":"application/json; charset=utf-8",...l.headers??{}}),t.end(JSON.stringify(l.body));return}if(a.stream==="bytes"){await De(n,t,d,p.searchParams);return}if(a.stream){await nt(n,t,S,d,Ze(n,p.searchParams),h,"sse",f.stream);return}if(a.name==="llm"){const s=we(await N(n,Oe)),g=new AbortController,l=i(()=>g.abort(),"abort");t.on("close",l);try{const E=await ye(s,g.signal,s.stream?A=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(A)}
20
+ `),_())}},"checkIdle"),N=setInterval(()=>{E().catch(u=>He({level:"error",event:"api.run_stream.idle_check_failed",error:u}))},I.idleCheckMs??1e3);return N.unref(),r.on("drain",h),r.on("close",_),await a(),P}i(ot,"streamRunEvents");async function zt(e,r,o,p={}){const f=e.listener;if(!e.issuer||!e.audience||!f?.address||!Number.isInteger(f.port)||f.port<1||f.port>65535)throw new Error("runtime issuer, audience and listener address/port are required");if(["0.0.0.0","::","[::]"].includes(f.address))throw new Error("runtime listener must bind a specific address");const l=new le(e.issuer,e.audience,o,e.jwksUrl),b=new V,I=new Map;for(const n of te.filter(t=>t.stage!==void 0)){const t=i(()=>({status:500}),"handler");b.register({method:n.method,pattern:n.path,handler:t}),I.set(t,n)}const H=new V().registerAll(K).registerAll(Z).registerAll(ee),w=W((n,t)=>{(async()=>{Ae(n,t,p.deferHead);const y=Q();t.setHeader("Request-Id",y);const O=de(L(n.headers.traceparent))??ce();t.setHeader(ae,B()),t.setHeader(ie,X);let T;try{const P=L(n.headers[re.toLowerCase()]),_=oe(P,B())??ue(L(n.headers[se.toLowerCase()]),X,{legacyMinimum:P,runtimeVersion:B()});if(_!==null)throw _.status===400?new m(400,_.code,_.message,_.param):new m(426,_.code,_.message,void 0,_.details);const h=new URL(n.url??"/","http://runtime.local");if(je(h.pathname)){await De(n,t,h,l);return}const R=b.match(n.method??"GET",h.pathname),a=R?I.get(R.handler):void 0,M=a?.name==="runtime.sync-notice"||a?.name==="runtime.vendor-status"||a?.name==="runtime.vendor-account-remove";let A,k;if(!M&&n.headers.authorization!==void 0)try{A=await l.verifyCall(n.headers.authorization)}catch(s){k=s}const D={issuer:e.issuer,jwks:l.jwks,audience:i(async()=>e.audience,"audience"),owner:fe,self:he,syncGrants:o,ready:i(async()=>l.isReady()&&await r(),"ready")};if(A?.chain[0]?.sub.startsWith("runtime:")){await z(n,t,D,A);return}if(A===void 0&&a===void 0&&await me(n.method??"GET",h.pathname)){await z(n,t,D,{status:401,code:"unauthorized",message:k===void 0?"a machine token is required":"invalid machine token"});return}if(a?.name==="ready"){const s=await r(),v=await ne({jwksReady:l.isReady(),grantsReady:i(()=>s,"grantsReady")});g(t,v.ready?200:503,v);return}if((n.method!=="GET"||h.pathname!=="/v1/health")&&(!l.isReady()||!await r()))throw new m(503,"runtime_starting","runtime_starting");if(!R||!a)throw new m(404,"not_found","unknown operation");if(T=R.params.run_id,a.name==="health"){g(t,200,{status:"ok",api_version:"2026-09-23"});return}if(a.name==="runtime.sync-notice"){await pe(await l.verifySyncNotice(n.headers.authorization),o,y),t.writeHead(204),t.end();return}if(a.name==="runtime.vendor-status"){await l.verifyDirectoryRead(n.headers.authorization,"vendor_status"),g(t,200,Xe());return}if(a.name==="runtime.vendor-account-remove"){await l.verifyDirectoryRead(n.headers.authorization,"vendor_account_remove"),g(t,200,await $e(await C(n)));return}if(A===void 0)throw k??new m(401,"unauthorized","a directory access token is required");const c=await l.resolveVerified(A,a.scope);if(t.setHeader("Request-Id",y),T&&await J(c,T,["run.message","run.interrupt","run.cancel","run.rename","run.delete"].includes(a.name),a.name==="run.delete"),a.stage==="objects"){const s=H.match(a.method,h.pathname);if(!s)throw new m(404,"not_found","unknown operation");const v={method:a.method,path:h.pathname,params:s.params,query:h.searchParams,body:await C(n,s.maxBodyBytes),ceiling:c.resolved,principal:{class:"app",app:c.grantee,scopes:c.resolved},req:n,res:t};let d;try{d=await s.handler(v)}catch(E){const{status:N,body:u}=q(E);throw N>=500&&console.error("[app-listener]",`trace_id=${O}`,E),new m(N,u.error.code,u.error.message,void 0,u.error.details)}if(t.headersSent)return;if(d.rawBody!==void 0||d.status===204||d.body===void 0){t.writeHead(d.status,d.headers??{}),t.end(d.rawBody);return}t.writeHead(d.status,{"Content-Type":"application/json; charset=utf-8",...d.headers??{}}),t.end(JSON.stringify(d.body));return}if(a.stream==="bytes"){await Be(n,t,c,h.searchParams);return}if(a.stream){await ot(n,t,T,c,nt(n,h.searchParams),y,"sse",p.stream,O);return}if(a.name==="llm"){const s=ve(await C(n,Te)),v=new AbortController,d=i(()=>v.abort(),"abort");t.on("close",d);try{const E=await ge(s,v.signal,s.stream?N=>{t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.write(`data: ${JSON.stringify(N)}
21
21
 
22
- `)}:void 0,void 0,{grantee:d.grantee,spend_cap:d.spend_cap},he(await $(d.grant)??await Ne(d.grant)));s.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
22
+ `)}:void 0,void 0,{grantee:c.grantee,spend_cap:c.spend_cap},_e(await F(c.grant)??await Pe(c.grant)));s.stream?(t.headersSent||t.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache","X-Accel-Buffering":"no"}),t.end(`data: [DONE]
23
23
 
24
- `)):R(t,200,E.completion)}finally{t.off("close",l)}return}if(a.name==="usage.get"){R(t,200,await ve(d.grantee,d.spend_cap,_e(p.searchParams.get("month"))));return}if(a.name==="profile.list"){R(t,200,Me(d));return}if(Je.has(a.name)){const s=await Be(a.name,d,O.params.question_id,a.method==="POST"?await N(n):void 0);R(t,s.status,s.body);return}if(Fe.has(a.name)){const{signal:s,release:g}=ze(t);try{const l=await Ge(a.name,$e(d),{params:O.params,body:a.method==="POST"?await N(n):void 0,query:p.searchParams},{requestId:h,signal:s});R(t,l.status,l.body)}catch(l){const E=Ye(l,h,!0);t.writeHead(E.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...E.headers}),t.end(JSON.stringify(E.body))}finally{g()}return}if(Xe.has(a.name)){const s=await Ve(a.name,{class:"app",app:d.grantee,scopes:d.resolved},p.searchParams);R(t,s.status,s.body);return}if(xe.has(a.name)){const s=await je(a,d,O.params,p.searchParams,await N(n));R(t,s.status,s.body);return}const M=await fe(a,d,S,p.searchParams,await N(n),n.headers["idempotency-key"]);R(t,M.status,M.body)}catch(I){Ke(t,I,h,S)}})()});return{ready:new Promise((n,t)=>{y.once("error",t),y.listen(u.port,u.address,()=>{y.off("error",t),n()})}),close:i(async()=>{w.close(),Ce(),y.closeAllConnections(),await new Promise((n,t)=>y.close(h=>h?t(h):n()))},"close")}}i(Ut,"createAppListener");export{tt as RUN_STREAM_IDLE_MS,Ut as createAppListener,Ze as cursor,nt as streamRunEvents};
24
+ `)):g(t,200,E.completion)}finally{t.off("close",d)}return}if(a.name==="usage.get"){g(t,200,await Oe(c.grantee,c.spend_cap,Re(h.searchParams.get("month"))));return}if(a.name==="profile.list"){g(t,200,xe(c));return}if(Ve.has(a.name)){const s=await Ue(a.name,c,R.params.question_id,a.method==="POST"?await C(n):void 0);g(t,s.status,s.body);return}if(We.has(a.name)){const{signal:s,release:v}=Fe(t);try{const d=await Qe(a.name,Ye(c),{params:R.params,body:a.method==="POST"?await C(n):void 0,query:h.searchParams},{requestId:y,traceId:O,signal:s});g(t,d.status,d.body)}catch(d){const E=qe(d,y,!0);t.writeHead(E.status,{"Content-Type":"application/json; charset=utf-8","X-Content-Type-Options":"nosniff",...E.headers}),t.end(JSON.stringify(E.body))}finally{v()}return}if(Ge.has(a.name)){const s=await ze(a.name,{class:"app",app:c.grantee,scopes:c.resolved},h.searchParams);g(t,s.status,s.body);return}if(Le.has(a.name)){const s=await Je(a,c,R.params,h.searchParams,await C(n));g(t,s.status,s.body);return}const j=await we(a,c,T,h.searchParams,await C(n),n.headers["idempotency-key"]);g(t,j.status,j.body)}catch(P){tt(t,P,y,O,T)}})()});return{ready:new Promise((n,t)=>{w.once("error",t),w.listen(f.port,f.address,()=>{w.off("error",t),n()})}),close:i(async()=>{l.close(),ke(),w.closeAllConnections(),await new Promise((n,t)=>w.close(y=>y?t(y):n()))},"close")}}i(zt,"createAppListener");export{at as RUN_STREAM_IDLE_MS,zt as createAppListener,nt as cursor,ot as streamRunEvents};
@@ -48,9 +48,11 @@ export declare function parseProvider(value: unknown): string;
48
48
  export declare function parseCallInput(body: unknown): ProviderCallInput;
49
49
  /** Per-request context the call path needs: the audit `request_id` (also the
50
50
  * `Crtr-Request-Id` a provider receives) and the signal that aborts the call
51
- * when the caller's socket closes (R9). */
51
+ * when the caller's socket closes (R9). `traceId` is the trace of the inbound HTTP request;
52
+ * without one the call starts a fresh trace. */
52
53
  export interface ProviderRequest {
53
54
  requestId: string;
55
+ traceId?: string;
54
56
  signal: AbortSignal;
55
57
  }
56
58
  export interface ProviderInput {
@@ -0,0 +1,6 @@
1
+ /** The trace id of a version-`00` `traceparent` whose trace id and parent id are lowercase hex and not all zeros, else null. */
2
+ export declare function parseTraceparent(value: string | undefined): string | null;
3
+ /** A fresh nonzero 16-byte trace id as 32 lowercase hex. */
4
+ export declare function newTraceId(): string;
5
+ /** The `traceparent` value for one outbound call: the trace id unchanged, a new 8-byte parent id, flags `00` (nothing sampled). */
6
+ export declare function outboundTraceparent(traceId: string): string;
@@ -0,0 +1 @@
1
+ var o=Object.defineProperty;var e=(t,n)=>o(t,"name",{value:n,configurable:!0});import{randomBytes as r}from"node:crypto";const f=/^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;function a(t){const n=t===void 0?null:f.exec(t);return n===null||/^0+$/.test(n[1])||/^0+$/.test(n[2])?null:n[1]}e(a,"parseTraceparent");function i(){for(;;){const t=r(16).toString("hex");if(!/^0+$/.test(t))return t}}e(i,"newTraceId");function s(t){for(;;){const n=r(8).toString("hex");if(!/^0+$/.test(n))return`00-${t}-${n}-00`}}e(s,"outboundTraceparent");export{i as newTraceId,s as outboundTraceparent,a as parseTraceparent};
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: Let other apps build on yours
3
+ description: Offer your app as a service other Northlight apps build on — as a provider their runs call, or as your own API — and list it with a docs link so their coding agents find it.
4
+ ---
5
+
6
+ # Let other apps build on yours
7
+
8
+ Other developers' apps can build on yours the way they build on Northlight's: by calling your provider tools from a run, or by calling your own API. Set a docs link on your app and it is listed on [Services you can build on](/docs/services), where their coding agents look.
9
+
10
+ Comms is built this way. Another app calls it with its own Northlight app key, and reads [comms.northlight.ai/llms.txt](https://comms.northlight.ai/llms.txt) to learn how:
11
+
12
+ ```ts
13
+ import {readFile} from 'node:fs/promises';
14
+ import {CommsClient} from '@north-light/comms';
15
+
16
+ const comms = new CommsClient({
17
+ baseUrl: 'https://comms.northlight.ai',
18
+ issuer: 'https://account.northlight.ai',
19
+ clientId: process.env.CROUTER_CLIENT_ID!, // the calling app's own client id and key
20
+ privateKeyJwk: JSON.parse(await readFile(process.env.CROUTER_KEY_FILE!, 'utf8')),
21
+ });
22
+ ```
23
+
24
+ ## Two ways to offer it
25
+
26
+ - **As a provider.** Your tools run on the person's runtime for any app the person has granted them, and you can charge per call. This is the same work as [letting the person's AI use your app](/docs/let-their-ai-use-your-app); other apps ask for your groups as scopes named `<your client id>:<group>`.
27
+ - **As your own API.** Serve HTTP from your own host, as Comms does. A calling app identifies itself with its Northlight client id and key, so you know which app is asking.
28
+
29
+ ## Get listed
30
+
31
+ 1. In the console, open your app's **Profile** tab.
32
+ 2. Under **Marketplace**, set **Docs link** to the https URL of your guide for agents and developers, for example `https://yourapp.com/llms.txt`, and choose **Save docs link**.
33
+ 3. Write that guide for a coding agent: what your service does, how to install the client, one working snippet, the errors and their fixes. [comms.northlight.ai/llms.txt](https://comms.northlight.ai/llms.txt) is a working example.
34
+
35
+ [Services you can build on](/docs/services) is rebuilt from the directory's public marketplace list each time this site is built, so a new docs link appears with no change to these docs.
36
+
37
+ ## Guides
38
+
39
+ | Page | What it covers |
40
+ |---|---|
41
+ | [Let the person's AI use your app](/docs/let-their-ai-use-your-app) | Define tools with `@north-light/plugin`, serve them, add the provider role, set a price |
42
+ | [Services you can build on](/docs/services) | Every listed app with a docs link |
43
+ | [Manage your app](/docs/guides/manage-your-app) | The console's **Profile** and **Provider** tabs |
44
+ | [Go live](/docs/guides/go-live) | Verification and the marketplace listing |
package/docs/apps/cli.md CHANGED
@@ -5,7 +5,7 @@ description: The npx @north-light/sdk commands for scaffolding an app, logging i
5
5
 
6
6
  # CLI reference
7
7
 
8
- `npx @north-light/sdk <command>` runs the package's one command-line tool. Run it with no command, or with `--help`, to print the list below. `npx @north-light/sdk <command> --help` (or `-h`) prints that command's usage and exits 0. An unknown command, or a bad flag, prints the usage and exits 1.
8
+ `npx @north-light/sdk <command>` runs the command-line tool of [`@north-light/sdk`](https://www.npmjs.com/package/@north-light/sdk) on npm. (crouter's own CLI, `crtr`, is a different package: [`@north-light/cli`](https://www.npmjs.com/package/@north-light/cli). Tools you offer to agents are built with [`@north-light/plugin`](https://www.npmjs.com/package/@north-light/plugin).) Run it with no command, or with `--help`, to print the list below. `npx @north-light/sdk <command> --help` (or `-h`) prints that command's usage and exits 0. An unknown command, or a bad flag, prints the usage and exits 1.
9
9
 
10
10
  With no dependencies installed, `npx` downloads the newest published release of the SDK (the one tagged latest on npm), not the version your project pins. Run `npm i` in the project first if the pinned version must run; `init` pins the version of the CLI that ran it.
11
11
 
@@ -21,7 +21,7 @@ With no dependencies installed, `npx` downloads the newest published release of
21
21
  | `npx @north-light/sdk register [--name <text>] [--scope <scope>]... [--logo <url>] [--justification <text>] [--redirect-uri <uri>]... [--issuer <url>]` | Run in a project made by `init`, after `login`: registers the app with the public key from `local/app-key.json`, adds each `--scope` beyond `crtr:llm crtr:act`, and writes `CROUTER_CLIENT_ID` and `CROUTER_ISSUER` to `local/app.env`. | No |
22
22
  | `npx @north-light/sdk apps <leaf> [--app <client id>] [--issuer <url>] [leaf flags] [--json]` | Call one app-management leaf with the login and print its answer as JSON. Leaves: `mine`, `get`, `update`, `set-logo --file <path>`, `set-brand-color --color <#rrggbb|none>`, `set-redirect-uris`, `asks`, `set-scopes`, `set-requires`, `set-run-notice-url`, `keys list\|add\|retire\|delete\|set-jwks-uri\|remove-jwks-uri`, `testers list\|add\|remove`. `--app` defaults to `CROUTER_CLIENT_ID` in `local/app.env`; `keys add` takes `--key-file <path>` and sends only the public half. Deleting an app, its marketplace listing, monthly spend cap, sharing and provider role are console-only: those leaves exit 1 and name the console page, with no request. | No |
23
23
 
24
- `keygen` and `env` are the only commands that can call `crtr`, and only with `--app`. The documented path on this site never uses `--app`, so you do not need crtr. `login`, `logout`, `whoami`, `register` and `apps` call only the Northlight directory.
24
+ `keygen` and `env` are the only commands that can call `crtr`, and only with `--app`. The rest of this site never uses `--app`, so crtr is optional; to use it, see [Run `crtr cloud apps` yourself](/docs/get-started#run-crtr-cloud-apps-yourself). `login`, `logout`, `whoami`, `register` and `apps` call only the Northlight directory.
25
25
 
26
26
  ## Details
27
27
 
@@ -63,7 +63,7 @@ Writes the `northlight` Agent Skill that ships in the package, replacing an olde
63
63
 
64
64
  ### `keygen`
65
65
 
66
- Writes a private signing key to `local/app-key.json` with mode `0600`, refuses to overwrite an existing file (pass `--out` for another path), and prints the public JWK on stdout. Register the JWK in the console with **Add key** on the app's **Sign-in** tab, or paste it under **Register an app**. With `--app <client id>`, it offers to add the key to your app through your own `crtr` (`crtr cloud apps keys add`); `--yes` skips the question. This needs a joined crtr, which this site does not otherwise require.
66
+ Writes a private signing key to `local/app-key.json` with mode `0600`, refuses to overwrite an existing file (pass `--out` for another path), and prints the public JWK on stdout. Register the JWK in the console with **Add key** on the app's **Sign-in** tab, or paste it under **Register an app**. With `--app <client id>`, it offers to add the key to your app through your own `crtr` (`crtr cloud apps keys add`); `--yes` skips the question. This needs a joined crtr.
67
67
 
68
68
  ### `env`
69
69
 
@@ -5,7 +5,7 @@ description: What npx @north-light/sdk init and skill install for Claude Code, C
5
5
 
6
6
  # Use your coding agent
7
7
 
8
- The `northlight` skill teaches a coding agent the whole path in the [quick setup](/docs/quickstart): the commands, the code patterns and the rules. With it, your agent signs you up, logs in, registers the app and runs it, and you choose **Allow** twice in your browser: once on the login page, once on the app's consent page. `npx @north-light/sdk init` installs it into your project. The skill needs no account and no crtr.
8
+ The `northlight` skill teaches a coding agent the whole path in the [quick setup](/docs/quickstart): the commands, the code patterns and the rules. With it, your agent signs you up, logs in, registers the app and runs it, and you choose **Allow** twice in your browser: once on the login page, once on the app's consent page. `npx @north-light/sdk init` installs it into your project. The skill needs no account to install, and works whether or not crouter is on your machine.
9
9
 
10
10
  ## Install it
11
11
 
@@ -25,6 +25,17 @@ npx @north-light/sdk skill
25
25
 
26
26
  `skill` always replaces the `northlight` skill it finds. `--print` writes the skill to stdout instead of to files.
27
27
 
28
+ ## Install it once for every project (Claude Code)
29
+
30
+ If you build more than one app, install the Claude Code plugin instead of a copy in each project:
31
+
32
+ ```
33
+ /plugin marketplace add Northlight-Systems/plugins
34
+ /plugin install northlight
35
+ ```
36
+
37
+ It adds the `northlight` skill, one skill for each section of this site, and the commands `/northlight:new-app`, `/northlight:add-permission`, `/northlight:text-people`, `/northlight:become-a-provider` and `/northlight:go-live`. The plugin repository is generated from these docs and the skill at each release, so it matches the current SDK version.
38
+
28
39
  ## Where it goes
29
40
 
30
41
  | Agent | In the project (default) | With `--global` |
@@ -36,7 +47,7 @@ npx @north-light/sdk skill
36
47
 
37
48
  The project copy is the default so it is committed with the project, and teammates get it from git. It matches the SDK version your project pins.
38
49
 
39
- Checked on production: the files `init` writes, that `init` in a directory that already has a `package.json` pins the SDK and writes the key and skill but no app code, and that `skill` rewrites both skill files. In such a directory `init` creates a `.gitignore` with `local/`, or adds `local/` to the one you have, so the key stays out of git. Which agent loads which directory is from each agent's documentation and is not yet checked on production for every agent.
50
+ In a directory that already has a `package.json`, `init` creates a `.gitignore` with `local/`, or adds `local/` to the one you have, so the key stays out of git.
40
51
 
41
52
  ## Without a project: the hosted skill
42
53
 
@@ -50,7 +61,7 @@ If the agent's shell call times out or is interrupted while you approve, it runs
50
61
 
51
62
  The skill tells the agent to read the whole message `login` printed and its exit code before it tells you what happened.
52
63
 
53
- Checked on production with Claude Code (an interactive session, no crtr on `PATH`, an empty directory), given only the skill URL plus the quickstart request: a fresh session ran `init`, `login`, `npm i`, `register --scope sms:send` and `npm start` by itself. The person's part was choosing **Allow** on the login page and on the app's consent page, and pressing the button; the run replied `pong` and the text was sent (whether it reached the phone is not checked). When the first login code expired unapproved, that session said so and ran `login` again for a new code. In an earlier session on the same day, before this guidance, the agent misread a failed second `login` as a network timeout and fell back to the console steps. Name what you want built in the prompt.
64
+ Name what you want built in the prompt.
54
65
 
55
66
  ## What the login can do
56
67
 
@@ -65,13 +76,11 @@ Checked on production with Claude Code (an interactive session, no crtr on `PATH
65
76
  | Change signing keys, redirect URIs and the run notice URL on an app in testing | Change those on a verified app: do it in the console |
66
77
  | | Approve another login |
67
78
 
68
- Deleting an app, its listing, spend cap and sharing stay in the console on purpose. The CLI refuses them without sending a request, and the directory refuses them (403 `provider_refused`) if a call is made by hand. Checked on production for delete, the marketplace listing and the spend cap.
79
+ Deleting an app, its listing, spend cap and sharing stay in the console on purpose. The CLI refuses them without sending a request, and the directory refuses them (403 `provider_refused`) if a call is made by hand.
69
80
 
70
81
  The login lives in `~/.config/northlight/credentials.json` (`$XDG_CONFIG_HOME/northlight/credentials.json` when set), mode `0600`, outside your project. The CLI refuses a file that other users can read. It lasts 30 days, or until it has gone 14 days unused, and then you run `login` again. An account holds at most 10 logins; an eleventh replaces the oldest. The Developer access page shows the date the login goes unused, "Expires Oct 31" for a login approved on Oct 17, not the 30-day date the CLI prints.
71
82
 
72
- Revoke it on your account's Developer access page (`https://account.northlight.ai/account/developer`), or run `npx @north-light/sdk logout`, which revokes it on the server and removes it from the file. The file is not deleted: it is rewritten as `{"version":1,"issuers":{}}`, with no secret in it. A revoked login stops working at once: `whoami` and every other command fail with "This login was revoked" and ask for `login`, and a token minted before the revoke is refused with 401 `credential_revoked` even before it expires. Checked on production: the Developer access page, Revoke, `logout`, and what the next command prints.
73
-
74
- Not yet checked on production: the 10-login limit and the eleventh login replacing the oldest, and the 14-day and 30-day expiries.
83
+ Revoke it on your account's Developer access page (`https://account.northlight.ai/account/developer`), or run `npx @north-light/sdk logout`, which revokes it on the server and removes it from the file. The file is not deleted: it is rewritten as `{"version":1,"issuers":{}}`, with no secret in it. A revoked login stops working at once: `whoami` and every other command fail with "This login was revoked" and ask for `login`, and a token minted before the revoke is refused with 401 `credential_revoked` even before it expires.
75
84
 
76
85
  ## How it stays current
77
86
 
@@ -76,7 +76,3 @@ LoggerModule.forRoot({
76
76
  ```
77
77
 
78
78
  Test it: send a callback with a recognizable fake code and assert that the code, the state and the cookie never appear in the log output.
79
-
80
- ## Checked and not checked
81
-
82
- Checked on production: the discovery document lists `S256` and `private_key_jwt`, and the login route of the quickstart server redirects to `https://account.northlight.ai/authorize` with the PKCE challenge and nonce. The rest is from the SDK and directory source. A full sign-in as the account that owns the app, through the consent page and the callback, was run on production with the quickstart server. A sign-in by a second person, token refresh and the refusals are not yet checked on production.