@caracalai/sdk 0.1.3-rc.1 → 0.1.5-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import { type CaracalContext } from "./context.js";
2
2
  import { type HeaderGetter } from "./envelope.js";
3
3
  import { type CoordinatorClient } from "./coordinator.js";
4
- import { AgentKind, type DelegationConstraints } from "./coordinator.js";
4
+ import { type Grant, type ServiceAgent } from "./primitives.js";
5
+ import { type DelegationConstraints } from "./coordinator.js";
5
6
  import type { JsonObject } from "./json.js";
6
7
  export interface ResourceBinding {
7
8
  resourceId: string;
@@ -16,17 +17,26 @@ export interface CaracalConfig {
16
17
  tokenSource?: TokenSource;
17
18
  gatewayUrl?: string;
18
19
  resources?: ResourceBinding[];
19
- defaultKind?: AgentKind;
20
20
  defaultTtlSeconds?: number;
21
21
  }
22
22
  export interface SpawnOptions {
23
- kind?: AgentKind;
23
+ grant?: Grant;
24
24
  ttlSeconds?: number;
25
25
  subjectSessionId?: string;
26
26
  parentId?: string;
27
27
  metadata?: JsonObject;
28
+ labels?: string[];
28
29
  traceId?: string;
29
30
  }
31
+ export interface ServiceOptions {
32
+ ttlSeconds?: number;
33
+ subjectSessionId?: string;
34
+ parentId?: string;
35
+ metadata?: JsonObject;
36
+ labels?: string[];
37
+ traceId?: string;
38
+ heartbeatIntervalMs?: number;
39
+ }
30
40
  export interface DelegateOptions {
31
41
  to: string;
32
42
  toApplicationId: string;
@@ -35,16 +45,6 @@ export interface DelegateOptions {
35
45
  constraints?: DelegationConstraints;
36
46
  ttlSeconds?: number;
37
47
  }
38
- export interface DelegateToSpawnOptions {
39
- resourceId?: string;
40
- scopes: string[];
41
- constraints?: DelegationConstraints;
42
- delegationTtlSeconds?: number;
43
- kind?: AgentKind;
44
- ttlSeconds?: number;
45
- metadata?: JsonObject;
46
- traceId?: string;
47
- }
48
48
  export type LifecycleHook = (ctx: CaracalContext) => void | Promise<void>;
49
49
  export interface RootOptions {
50
50
  allowRoot?: boolean;
@@ -58,6 +58,7 @@ export interface ClientSecretOptions {
58
58
  resources: Array<string | ResourceBinding>;
59
59
  gatewayUrl?: string;
60
60
  scope?: string;
61
+ fetchImpl?: typeof fetch;
61
62
  }
62
63
  export interface ConnectOptions {
63
64
  env?: NodeJS.ProcessEnv;
@@ -82,8 +83,8 @@ export declare class Caracal {
82
83
  static fromConfig(path?: string, env?: NodeJS.ProcessEnv): Caracal;
83
84
  close(): Promise<void>;
84
85
  spawn<T>(fn: () => Promise<T>, opts?: SpawnOptions): Promise<T>;
86
+ service(opts?: ServiceOptions): Promise<ServiceAgent>;
85
87
  delegate<T>(opts: DelegateOptions, fn: () => Promise<T>): Promise<T>;
86
- delegateToSpawn<T>(opts: DelegateToSpawnOptions, fn: () => Promise<T>): Promise<T>;
87
88
  bind<T>(ctx: CaracalContext, fn: () => Promise<T>): Promise<T>;
88
89
  onAgentStart(cb: LifecycleHook): void;
89
90
  onAgentEnd(cb: LifecycleHook): void;
@@ -100,6 +101,13 @@ export declare class Caracal {
100
101
  */
101
102
  transport(opts?: RootOptions): typeof fetch;
102
103
  gatewayRequest(resourceId: string, path?: string): GatewayRequest;
104
+ /**
105
+ * One-call happy path: sends `init` to `path` on the given resource through the
106
+ * Gateway with Caracal context and authority injected. Equivalent to building a
107
+ * `gatewayRequest` and calling it with `transport`. The resource header always
108
+ * wins over any caller-supplied `X-Caracal-Resource`.
109
+ */
110
+ fetch(resourceId: string, path?: string, init?: RequestInit, opts?: RootOptions): Promise<Response>;
103
111
  private routeThroughGateway;
104
112
  /**
105
113
  * Binds Caracal context after a verifier boundary. This does not verify JWT
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/client.js CHANGED
@@ -6,13 +6,15 @@
6
6
  */
7
7
  import { bind, fromEnvelope, toEnvelope, current } from "./context.js";
8
8
  import { existsSync, readFileSync, statSync } from "node:fs";
9
- import { homedir } from "node:os";
9
+ import { homedir, platform } from "node:os";
10
10
  import { join } from "node:path";
11
11
  import { parse } from "smol-toml";
12
12
  import { decodeEnvelope, toHeaders, } from "./envelope.js";
13
- import { spawn as spawnPrimitive, delegate as delegatePrimitive, delegateToSpawn as delegateToSpawnPrimitive, } from "./primitives.js";
14
- import { AgentKind } from "./coordinator.js";
13
+ import { spawn as spawnPrimitive, spawnService as spawnServicePrimitive, delegate as delegatePrimitive, } from "./primitives.js";
15
14
  import { OAuthClient } from "@caracalai/oauth";
15
+ const DEFAULT_STS_URL = "http://localhost:8080";
16
+ const DEFAULT_COORDINATOR_URL = "http://localhost:4000";
17
+ const DEFAULT_GATEWAY_URL = "http://localhost:8081";
16
18
  export class Caracal {
17
19
  config;
18
20
  agentStartHooks = [];
@@ -54,38 +56,35 @@ export class Caracal {
54
56
  return Caracal.fromEnv(env);
55
57
  }
56
58
  static fromEnv(env = process.env) {
57
- const url = env.CARACAL_COORDINATOR_URL;
59
+ const url = serviceUrl(env, "CARACAL_COORDINATOR_URL", DEFAULT_COORDINATOR_URL);
58
60
  const zoneId = env.CARACAL_ZONE_ID;
59
61
  const applicationId = env.CARACAL_APPLICATION_ID;
60
62
  const subjectToken = env.CARACAL_SUBJECT_TOKEN;
61
- const clientSecret = clientSecretFromEnv(env);
62
- const stsUrl = env.CARACAL_STS_URL;
63
- const gatewayUrl = env.CARACAL_GATEWAY_URL;
63
+ const stsUrl = stsUrlFromEnv(env);
64
+ const gatewayUrl = serviceUrl(env, "CARACAL_GATEWAY_URL", DEFAULT_GATEWAY_URL);
64
65
  const missing = [
65
- ["CARACAL_COORDINATOR_URL", url],
66
66
  ["CARACAL_ZONE_ID", zoneId],
67
67
  ["CARACAL_APPLICATION_ID", applicationId],
68
68
  ].filter(([, v]) => !v).map(([k]) => k);
69
69
  if (missing.length) {
70
70
  throw new Error(`Caracal.fromEnv: missing ${missing.join(", ")}`);
71
71
  }
72
- const profileResources = resourcesFromEnv(env);
72
+ const clientSecret = clientSecretFromEnv(env, zoneId, applicationId);
73
+ const profileResources = resourcesFromEnv(env, zoneId, applicationId);
73
74
  const resources = profileResources.bindings;
74
75
  if (clientSecret) {
75
- if (!stsUrl)
76
- throw new Error("Caracal.fromEnv: CARACAL_APP_CLIENT_SECRET requires CARACAL_STS_URL");
77
76
  return Caracal.fromClientSecret({
78
77
  coordinatorUrl: url,
79
78
  stsUrl,
80
79
  zoneId: zoneId,
81
80
  applicationId: applicationId,
82
81
  clientSecret,
83
- resources: resourceIdsFromEnv(env.CARACAL_APP_RESOURCES, profileResources.resources),
82
+ resources: resourceIdsFromEnv(env.CARACAL_APP_RESOURCES, profileResources.credentialResources ?? [], profileResources.resources),
84
83
  gatewayUrl,
85
84
  });
86
85
  }
87
86
  if (!subjectToken) {
88
- throw new Error("Caracal.fromEnv: provide CARACAL_APP_CLIENT_SECRET (+ CARACAL_STS_URL) or CARACAL_SUBJECT_TOKEN");
87
+ throw new Error("Caracal.fromEnv: provide CARACAL_APP_CLIENT_SECRET or CARACAL_SUBJECT_TOKEN");
89
88
  }
90
89
  validateSubjectToken(subjectToken);
91
90
  return new Caracal({
@@ -106,7 +105,7 @@ export class Caracal {
106
105
  coordinator: { baseUrl: opts.coordinatorUrl },
107
106
  zoneId: opts.zoneId,
108
107
  applicationId: opts.applicationId,
109
- tokenSource: createClientSecretTokenSource(opts.stsUrl, opts.zoneId, opts.applicationId, opts.clientSecret, resourceIds, opts.scope),
108
+ tokenSource: createClientSecretTokenSource(opts.stsUrl, opts.zoneId, opts.applicationId, opts.clientSecret, resourceIds, opts.scope, opts.fetchImpl),
110
109
  gatewayUrl: opts.gatewayUrl,
111
110
  resources: bindings.length ? bindings : undefined,
112
111
  });
@@ -120,21 +119,24 @@ export class Caracal {
120
119
  throw new Error("Caracal.fromConfig: profile must be a TOML table");
121
120
  const zoneId = requiredString(value, "zone_id", path);
122
121
  const applicationId = requiredString(value, "application_id", path);
123
- const stsUrl = stringValue(value, "sts_url") ?? stringValue(value, "zone_url") ?? env.CARACAL_STS_URL;
124
- if (!stsUrl)
125
- throw new Error("Caracal.fromConfig: sts_url or zone_url is required");
126
- const coordinatorUrl = stringValue(value, "coordinator_url") ?? env.CARACAL_COORDINATOR_URL;
127
- if (!coordinatorUrl)
128
- throw new Error("Caracal.fromConfig: coordinator_url is required");
129
- const resources = resourcesFromProfile(value, path);
122
+ const stsUrl = stringValue(value, "sts_url")
123
+ ?? stringValue(value, "zone_url")
124
+ ?? env.CARACAL_STS_URL
125
+ ?? env.CARACAL_ZONE_URL
126
+ ?? serviceUrl(env, "CARACAL_STS_URL", DEFAULT_STS_URL);
127
+ const coordinatorUrl = stringValue(value, "coordinator_url") ?? serviceUrl(env, "CARACAL_COORDINATOR_URL", DEFAULT_COORDINATOR_URL);
128
+ const resources = resourcesFromProfile(value, path, env, zoneId, applicationId);
129
+ if (!resources.resources.length) {
130
+ throw new Error(`Caracal.fromConfig: ${path} requires at least one resource via credentials, CARACAL_RESOURCES, or CARACAL_RESOURCES_FILE`);
131
+ }
130
132
  return Caracal.fromClientSecret({
131
133
  coordinatorUrl,
132
134
  stsUrl,
133
135
  zoneId,
134
136
  applicationId,
135
- clientSecret: clientSecretFromProfile(value, path),
137
+ clientSecret: clientSecretFromProfile(value, path, env, zoneId, applicationId),
136
138
  resources: resources.resources,
137
- gatewayUrl: stringValue(value, "gateway_url") ?? env.CARACAL_GATEWAY_URL,
139
+ gatewayUrl: stringValue(value, "gateway_url") ?? serviceUrl(env, "CARACAL_GATEWAY_URL", DEFAULT_GATEWAY_URL),
138
140
  });
139
141
  }
140
142
  async close() {
@@ -145,17 +147,34 @@ export class Caracal {
145
147
  zoneId: this.config.zoneId,
146
148
  applicationId: this.config.applicationId,
147
149
  subjectToken: await this.rootToken(),
148
- kind: opts.kind ?? this.config.defaultKind ?? AgentKind.Instance,
150
+ grant: opts.grant,
149
151
  ttlSeconds: opts.ttlSeconds ?? this.config.defaultTtlSeconds,
150
152
  subjectSessionId: opts.subjectSessionId,
151
153
  parentId: opts.parentId,
152
154
  metadata: opts.metadata,
155
+ labels: opts.labels,
153
156
  traceId: opts.traceId,
154
157
  onAgentStart: this.agentStartHooks.length ? (c) => this.fire(this.agentStartHooks, c) : undefined,
155
158
  onAgentEnd: this.agentEndHooks.length ? (c) => this.fire(this.agentEndHooks, c) : undefined,
156
159
  };
157
160
  return await spawnPrimitive(input, fn);
158
161
  }
162
+ async service(opts = {}) {
163
+ return await spawnServicePrimitive({
164
+ coordinator: this.config.coordinator,
165
+ zoneId: this.config.zoneId,
166
+ applicationId: this.config.applicationId,
167
+ subjectToken: await this.rootToken(),
168
+ ttlSeconds: opts.ttlSeconds ?? this.config.defaultTtlSeconds,
169
+ subjectSessionId: opts.subjectSessionId,
170
+ parentId: opts.parentId,
171
+ metadata: opts.metadata,
172
+ labels: opts.labels,
173
+ traceId: opts.traceId,
174
+ heartbeatIntervalMs: opts.heartbeatIntervalMs,
175
+ onAgentStart: this.agentStartHooks.length ? (c) => this.fire(this.agentStartHooks, c) : undefined,
176
+ });
177
+ }
159
178
  delegate(opts, fn) {
160
179
  const input = {
161
180
  coordinator: this.config.coordinator,
@@ -168,25 +187,6 @@ export class Caracal {
168
187
  };
169
188
  return delegatePrimitive(input, fn);
170
189
  }
171
- async delegateToSpawn(opts, fn) {
172
- const input = {
173
- coordinator: this.config.coordinator,
174
- zoneId: this.config.zoneId,
175
- applicationId: this.config.applicationId,
176
- subjectToken: await this.rootToken(),
177
- resourceId: opts.resourceId,
178
- scopes: opts.scopes,
179
- constraints: opts.constraints,
180
- delegationTtlSeconds: opts.delegationTtlSeconds,
181
- kind: opts.kind ?? this.config.defaultKind ?? AgentKind.Instance,
182
- ttlSeconds: opts.ttlSeconds ?? this.config.defaultTtlSeconds,
183
- metadata: opts.metadata,
184
- traceId: opts.traceId,
185
- onAgentStart: this.agentStartHooks.length ? (c) => this.fire(this.agentStartHooks, c) : undefined,
186
- onAgentEnd: this.agentEndHooks.length ? (c) => this.fire(this.agentEndHooks, c) : undefined,
187
- };
188
- return await delegateToSpawnPrimitive(input, fn);
189
- }
190
190
  bind(ctx, fn) {
191
191
  return bind(ctx, fn);
192
192
  }
@@ -293,6 +293,19 @@ export class Caracal {
293
293
  headers: { "X-Caracal-Resource": resourceId },
294
294
  };
295
295
  }
296
+ /**
297
+ * One-call happy path: sends `init` to `path` on the given resource through the
298
+ * Gateway with Caracal context and authority injected. Equivalent to building a
299
+ * `gatewayRequest` and calling it with `transport`. The resource header always
300
+ * wins over any caller-supplied `X-Caracal-Resource`.
301
+ */
302
+ fetch(resourceId, path = "/", init = {}, opts = {}) {
303
+ const request = this.gatewayRequest(resourceId, path);
304
+ const headers = new Headers(init.headers ?? {});
305
+ for (const [key, value] of Object.entries(request.headers))
306
+ headers.set(key, value);
307
+ return this.transport(opts)(request.url, { ...init, headers });
308
+ }
296
309
  routeThroughGateway(input, explicitResource) {
297
310
  const gw = this.config.gatewayUrl;
298
311
  if (!gw)
@@ -354,11 +367,54 @@ export class Caracal {
354
367
  throw new Error("Caracal client has no subject token source");
355
368
  }
356
369
  }
370
+ function serviceUrl(env, key, fallback) {
371
+ const value = env[key];
372
+ if (value)
373
+ return value;
374
+ if (env.NODE_ENV === "production")
375
+ throw new Error(`Caracal SDK: ${key} is required when NODE_ENV=production`);
376
+ return fallback;
377
+ }
378
+ function stsUrlFromEnv(env) {
379
+ return env.CARACAL_STS_URL ?? env.CARACAL_ZONE_URL ?? serviceUrl(env, "CARACAL_STS_URL", DEFAULT_STS_URL);
380
+ }
357
381
  function defaultProfilePath(env = process.env) {
358
- const xdg = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0
359
- ? env.XDG_CONFIG_HOME
360
- : join(homedir(), ".config");
361
- return join(xdg, "caracal", "caracal.toml");
382
+ return join(defaultConfigDir(env), "caracal.toml");
383
+ }
384
+ function defaultConfigDir(env = process.env) {
385
+ if (env.CARACAL_CONFIG_HOME)
386
+ return env.CARACAL_CONFIG_HOME;
387
+ if (env.XDG_CONFIG_HOME)
388
+ return join(env.XDG_CONFIG_HOME, "caracal");
389
+ if (platform() === "win32")
390
+ return join(env.APPDATA || env.LOCALAPPDATA || join(homedir(), "AppData", "Roaming"), "Caracal");
391
+ if (platform() === "darwin")
392
+ return join(homedir(), "Library", "Application Support", "Caracal");
393
+ return join(homedir(), ".config", "caracal");
394
+ }
395
+ function defaultCredentialDir(env, zoneId, applicationId) {
396
+ return join(defaultConfigDir(env), "runtime", safePathSegment(zoneId), safePathSegment(applicationId));
397
+ }
398
+ function defaultClientSecretPath(env, zoneId, applicationId) {
399
+ return join(defaultCredentialDir(env, zoneId, applicationId), "client-secret");
400
+ }
401
+ function defaultRunCredentialsPath(env, zoneId, applicationId) {
402
+ return join(defaultCredentialDir(env, zoneId, applicationId), "credentials.json");
403
+ }
404
+ function safePathSegment(value) {
405
+ const segment = value.trim().replace(/[^A-Za-z0-9._-]+/g, "_");
406
+ let start = 0;
407
+ let end = segment.length;
408
+ while (start < end && segment[start] === "_")
409
+ start += 1;
410
+ while (end > start && segment[end - 1] === "_")
411
+ end -= 1;
412
+ return segment.slice(start, end) || "default";
413
+ }
414
+ function existingLocalFile(path, env) {
415
+ if (env.NODE_ENV === "production")
416
+ return undefined;
417
+ return existsSync(path) ? path : undefined;
362
418
  }
363
419
  function resolveProfilePath(env) {
364
420
  if (env.CARACAL_CONFIG) {
@@ -400,23 +456,27 @@ function requiredString(record, key, source) {
400
456
  throw new Error(`${source}: ${key} is required`);
401
457
  return value;
402
458
  }
403
- function clientSecretFromProfile(record, source) {
459
+ function clientSecretFromProfile(record, source, env, zoneId, applicationId) {
404
460
  const inline = stringValue(record, "app_client_secret");
405
461
  const file = stringValue(record, "app_client_secret_file");
406
462
  if (inline && file)
407
463
  throw new Error(`${source}: set only one of app_client_secret or app_client_secret_file`);
408
464
  if (inline)
409
465
  return inline;
410
- if (!file)
411
- throw new Error(`${source}: app_client_secret_file is required`);
412
- return readSecretFile(file);
466
+ const localFile = file ?? existingLocalFile(defaultClientSecretPath(env, zoneId, applicationId), env);
467
+ if (!localFile)
468
+ throw new Error(`${source}: client secret is required; local dev/stable auto-detects ${defaultClientSecretPath(env, zoneId, applicationId)} when it exists`);
469
+ return readSecretFile(localFile);
413
470
  }
414
- function clientSecretFromEnv(env) {
471
+ function clientSecretFromEnv(env, zoneId, applicationId) {
415
472
  if (env.CARACAL_APP_CLIENT_SECRET && env.CARACAL_APP_CLIENT_SECRET_FILE) {
416
473
  throw new Error("Caracal.fromEnv: set only one of CARACAL_APP_CLIENT_SECRET or CARACAL_APP_CLIENT_SECRET_FILE");
417
474
  }
418
475
  if (env.CARACAL_APP_CLIENT_SECRET_FILE)
419
476
  return readSecretFile(env.CARACAL_APP_CLIENT_SECRET_FILE);
477
+ const localFile = existingLocalFile(defaultClientSecretPath(env, zoneId, applicationId), env);
478
+ if (localFile)
479
+ return readSecretFile(localFile);
420
480
  return env.CARACAL_APP_CLIENT_SECRET;
421
481
  }
422
482
  function readSecretFile(path) {
@@ -428,64 +488,123 @@ function readSecretFile(path) {
428
488
  throw new Error(`Caracal profile secret file is empty: ${path}`);
429
489
  return secret;
430
490
  }
431
- function resourcesFromProfile(record, source) {
491
+ function resourcesFromProfile(record, source, env, zoneId, applicationId) {
432
492
  const credentials = [
433
493
  ...credentialEntries(record.credentials, `${source}.credentials`),
434
494
  ...credentialEntries(record.optional_credentials, `${source}.optional_credentials`),
495
+ ...credentialManifestFromEnv(env, zoneId, applicationId),
435
496
  ];
436
497
  const resources = resourcesFromCredentials(credentials);
437
- if (!resources.resources.length)
438
- throw new Error(`${source}: at least one credentials or optional_credentials entry is required`);
439
- return resources;
440
- }
441
- function resourcesFromEnv(env) {
442
- const credentials = credentialManifestFromEnv(env);
443
- const envBindings = [
444
- ...resourceBindingsFromFile(env.CARACAL_RESOURCES_FILE),
445
- ...(parseResourceBindings(env.CARACAL_RESOURCES) ?? []),
446
- ];
498
+ const resolved = resolveProfileResources(resources.resources, resources.bindings ?? [], env);
499
+ return { ...resolved, credentialResources: resources.resources };
500
+ }
501
+ function resourcesFromEnv(env, zoneId, applicationId) {
502
+ const credentials = credentialManifestFromEnv(env, zoneId, applicationId);
447
503
  const resources = resourcesFromCredentials(credentials);
504
+ const resolved = resolveProfileResources(resources.resources, resources.bindings ?? [], env);
505
+ return { ...resolved, credentialResources: resources.resources };
506
+ }
507
+ function resolveProfileResources(resources, credentialBindings, env) {
508
+ const envBindings = parseResourceBindings(env.CARACAL_RESOURCES) ?? [];
509
+ const bindings = sortBindingsLongestFirst(mergeResourceBindings(credentialBindings, resourceBindingsFromFile(env.CARACAL_RESOURCES_FILE), envBindings));
448
510
  const byResource = new Map();
449
- for (const item of resources.resources)
511
+ for (const item of resources)
450
512
  byResource.set(typeof item === "string" ? item : item.resourceId, item);
451
- for (const binding of envBindings)
513
+ for (const binding of bindings)
452
514
  byResource.set(binding.resourceId, binding);
453
515
  const values = [...byResource.values()];
454
- return { resources: values, bindings: values.filter((value) => typeof value !== "string") };
516
+ return { resources: values, bindings };
455
517
  }
456
518
  function resourceBindingsFromFile(path) {
457
519
  if (!path)
458
520
  return [];
459
521
  const parsed = JSON.parse(readFileSync(path, "utf8"));
522
+ const errors = [];
460
523
  if (Array.isArray(parsed)) {
461
- return parsed.map((entry, index) => {
462
- if (!isRecord(entry))
463
- throw new Error(`CARACAL_RESOURCES_FILE[${index}] must be an object`);
464
- const resourceId = stringValue(entry, "resource_id");
465
- const upstreamPrefix = stringValue(entry, "upstream_prefix");
466
- if (!resourceId || !upstreamPrefix)
467
- throw new Error(`CARACAL_RESOURCES_FILE[${index}] requires resource_id and upstream_prefix`);
468
- return { resourceId, upstreamPrefix };
469
- });
524
+ const out = [];
525
+ for (const [index, entry] of parsed.entries()) {
526
+ if (!isRecord(entry)) {
527
+ errors.push(`[${index}]: entry must be an object`);
528
+ continue;
529
+ }
530
+ const keys = Object.keys(entry);
531
+ if (keys.length !== 2 || !keys.includes("resource_id") || !keys.includes("upstream_prefix")) {
532
+ errors.push(`[${index}]: expected exactly resource_id and upstream_prefix`);
533
+ continue;
534
+ }
535
+ const resourceId = entry.resource_id;
536
+ const upstreamPrefix = entry.upstream_prefix;
537
+ if (typeof resourceId !== "string" || !resourceId) {
538
+ errors.push(`[${index}]: resource_id must be a non-empty string`);
539
+ continue;
540
+ }
541
+ if (typeof upstreamPrefix !== "string" || !upstreamPrefix) {
542
+ errors.push(`[${index}]: upstream_prefix must be a non-empty string`);
543
+ continue;
544
+ }
545
+ if (!isAbsoluteUrl(upstreamPrefix)) {
546
+ errors.push(`[${index}]: upstream_prefix must be an absolute URL`);
547
+ continue;
548
+ }
549
+ out.push({ resourceId, upstreamPrefix });
550
+ }
551
+ if (errors.length)
552
+ throw new Error(`invalid CARACAL_RESOURCES_FILE:\n- ${errors.join("\n- ")}`);
553
+ return out;
470
554
  }
471
555
  if (isRecord(parsed)) {
472
- return Object.entries(parsed).map(([resourceId, upstreamPrefix]) => {
556
+ const out = [];
557
+ for (const [resourceId, upstreamPrefix] of Object.entries(parsed)) {
558
+ if (!resourceId) {
559
+ errors.push("key must be a non-empty string");
560
+ continue;
561
+ }
473
562
  if (typeof upstreamPrefix !== "string" || !upstreamPrefix) {
474
- throw new Error(`CARACAL_RESOURCES_FILE.${resourceId} must be a non-empty string`);
563
+ errors.push(`entry ${JSON.stringify(resourceId)} upstream_prefix must be a non-empty string`);
564
+ continue;
475
565
  }
476
- return { resourceId, upstreamPrefix };
477
- });
566
+ if (!isAbsoluteUrl(upstreamPrefix)) {
567
+ errors.push(`entry ${JSON.stringify(resourceId)} upstream_prefix must be an absolute URL`);
568
+ continue;
569
+ }
570
+ out.push({ resourceId, upstreamPrefix });
571
+ }
572
+ if (errors.length)
573
+ throw new Error(`invalid CARACAL_RESOURCES_FILE:\n- ${errors.join("\n- ")}`);
574
+ return out;
478
575
  }
479
576
  throw new Error("CARACAL_RESOURCES_FILE must contain an object or array");
480
577
  }
481
- function credentialManifestFromEnv(env) {
578
+ function mergeResourceBindings(...sources) {
579
+ const order = [];
580
+ const byResource = new Map();
581
+ for (const source of sources) {
582
+ for (const binding of source) {
583
+ if (!byResource.has(binding.resourceId))
584
+ order.push(binding.resourceId);
585
+ byResource.set(binding.resourceId, binding);
586
+ }
587
+ }
588
+ return order.map((resourceId) => byResource.get(resourceId));
589
+ }
590
+ function isAbsoluteUrl(value) {
591
+ try {
592
+ const parsed = new URL(value);
593
+ return Boolean(parsed.protocol && parsed.host);
594
+ }
595
+ catch {
596
+ return false;
597
+ }
598
+ }
599
+ function credentialManifestFromEnv(env, zoneId, applicationId) {
482
600
  const file = env.CARACAL_RUN_CREDENTIALS_FILE;
483
601
  const inline = env.CARACAL_RUN_CREDENTIALS;
484
602
  if (file && inline)
485
603
  throw new Error("Caracal.fromEnv: set only one of CARACAL_RUN_CREDENTIALS or CARACAL_RUN_CREDENTIALS_FILE");
486
- if (!file && !inline)
604
+ const localFile = !file && !inline ? existingLocalFile(defaultRunCredentialsPath(env, zoneId, applicationId), env) : undefined;
605
+ if (!file && !inline && !localFile)
487
606
  return [];
488
- const raw = file ? readSecretFile(file) : inline;
607
+ const raw = file || localFile ? readSecretFile(file ?? localFile) : inline;
489
608
  const parsed = JSON.parse(raw);
490
609
  const manifest = Array.isArray(parsed) ? { credentials: parsed } : parsed;
491
610
  if (!isRecord(manifest))
@@ -581,10 +700,7 @@ function parseResourceBindings(raw) {
581
700
  errors.push(`entry ${index + 1} must contain non-empty resourceId and upstreamPrefix`);
582
701
  continue;
583
702
  }
584
- try {
585
- new URL(upstreamPrefix);
586
- }
587
- catch {
703
+ if (!isAbsoluteUrl(upstreamPrefix)) {
588
704
  errors.push(`entry ${index + 1} upstreamPrefix must be an absolute URL`);
589
705
  continue;
590
706
  }
@@ -595,16 +711,28 @@ function parseResourceBindings(raw) {
595
711
  }
596
712
  return out.length ? sortBindingsLongestFirst(out) : undefined;
597
713
  }
598
- function resourceIdsFromEnv(raw, resources) {
714
+ function compactResourceValues(values) {
715
+ const seen = new Set();
716
+ const out = [];
717
+ for (const value of values) {
718
+ const resourceId = typeof value === "string" ? value : value.resourceId;
719
+ if (!resourceId || seen.has(resourceId))
720
+ continue;
721
+ seen.add(resourceId);
722
+ out.push(value);
723
+ }
724
+ return out;
725
+ }
726
+ function resourceIdsFromEnv(raw, first, resources) {
599
727
  const explicit = raw?.split(",").map((value) => value.trim()).filter(Boolean);
600
728
  if (explicit?.length)
601
- return explicit;
729
+ return compactResourceValues([...first, ...explicit]);
602
730
  if (resources?.length)
603
731
  return resources;
604
- throw new Error("Caracal.fromEnv: client-secret mode requires resources via CARACAL_APP_RESOURCES, CARACAL_RUN_CREDENTIALS, or CARACAL_RESOURCES");
732
+ throw new Error("Caracal.fromEnv: client-secret mode requires resources via CARACAL_APP_RESOURCES, CARACAL_RUN_CREDENTIALS, CARACAL_RESOURCES, or CARACAL_RESOURCES_FILE");
605
733
  }
606
- function createClientSecretTokenSource(stsUrl, zoneId, applicationId, clientSecret, resources, scope = "agent:lifecycle") {
607
- const client = new OAuthClient(stsUrl, zoneId, applicationId);
734
+ function createClientSecretTokenSource(stsUrl, zoneId, applicationId, clientSecret, resources, scope = "agent:lifecycle", fetchImpl) {
735
+ const client = new OAuthClient(stsUrl, zoneId, applicationId, undefined, fetchImpl);
608
736
  return async () => {
609
737
  const token = await client.exchange("", resources, { clientSecret, scopes: [scope] });
610
738
  return token.accessToken;