@tangle-network/agent-provider-tangle 1.2.1 → 1.2.2

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/README.md CHANGED
@@ -29,6 +29,37 @@ const provider = createTangleProvider({
29
29
  `Sandbox` requires an explicit `baseUrl`.
30
30
  Set `TANGLE_SANDBOX_URL` to use another deployment.
31
31
 
32
+ ## Named model credentials at creation
33
+
34
+ `modelCredentials` sends a stored credential reference and explicit endpoint before Sandbox provisions the backend.
35
+ This uses Sandbox's existing caller-owned model credential path instead of requesting a managed Router credential.
36
+ The provider does not create, grant, copy, or renew secrets.
37
+ Each create must list the named secret explicitly.
38
+
39
+ ```ts
40
+ const provider = createTangleProvider({
41
+ client,
42
+ modelCredentials: {
43
+ apiKeyEnv: "RESEARCH_ROUTER_KEY",
44
+ baseUrl: "https://router.tangle.tools/v1",
45
+ },
46
+ });
47
+ const environment = await provider.create({
48
+ profile: researchProfile,
49
+ secrets: ["RESEARCH_ROUTER_KEY"],
50
+ });
51
+ ```
52
+
53
+ The configuration accepts only the reference and endpoint, not credential values or model selection.
54
+ The authored profile, selected backend, runtime attachments, and create identity remain unchanged.
55
+ The provider snapshots this configuration when constructed and refuses combining it with `mapCreateInput`.
56
+ Omitting it preserves managed creation.
57
+ The endpoint must use HTTP or HTTPS without credentials, query parameters, or fragments.
58
+
59
+ Credential-only turns retain the created SDK handle's endpoint and attachments.
60
+ Reconstructed handles leave omitted model settings to Sandbox's durable backend configuration.
61
+ Keep the stored secret available and valid throughout execution and recovery.
62
+
32
63
  ## `create()` returns a ready environment
33
64
 
34
65
  `provider.create()` does not return until the sandbox reports `running`.
@@ -1,6 +1,8 @@
1
1
  import { type BackendType, type CreateSandboxOptions } from "@tangle-network/sandbox";
2
2
  import type { CreateAgentEnvironmentInput, WorkspaceRequest } from "@tangle-network/agent-interface/environment-provider";
3
- export declare function sandboxOptionsFromCreateInput(input: CreateAgentEnvironmentInput, defaultBackend?: BackendType, parsedWorkspace?: WorkspaceRequest): CreateSandboxOptions;
3
+ import type { TangleProviderOptions } from "./tangle-types.js";
4
+ export declare function captureModelCredentials(value: TangleProviderOptions["modelCredentials"]): TangleProviderOptions["modelCredentials"];
5
+ export declare function sandboxOptionsFromCreateInput(input: CreateAgentEnvironmentInput, defaultBackend?: BackendType, parsedWorkspace?: WorkspaceRequest, modelCredentials?: TangleProviderOptions["modelCredentials"]): CreateSandboxOptions;
4
6
  /** Reject value-bearing secret maps before any custom mapper can drop them. */
5
7
  export declare function assertNoInlineSecretValues(input: CreateAgentEnvironmentInput, parsedWorkspace?: WorkspaceRequest): void;
6
8
  /** A custom mapper must not smuggle a value map into the Sandbox request. */
@@ -3,13 +3,44 @@ import { AgentEnvironmentEgressPolicySchema, WorkspaceRequestSchema, workspaceCw
3
3
  import { assertBoundedJson, boundedIdentifier, boundedString, MAX_ARRAY_LENGTH, MAX_MAP_ENTRIES, } from "./tangle-contract-safety.js";
4
4
  import { sandboxResourcesFromResourceRequest } from "./tangle-resources.js";
5
5
  import { tangleRuntimeAttachments } from "./tangle-runtime-attachments.js";
6
- export function sandboxOptionsFromCreateInput(input, defaultBackend, parsedWorkspace) {
6
+ export function captureModelCredentials(value) {
7
+ if (value === undefined)
8
+ return undefined;
9
+ assertBoundedJson(value);
10
+ if (!value || typeof value !== "object" || Array.isArray(value) ||
11
+ Object.keys(value).some((key) => key !== "apiKeyEnv" && key !== "baseUrl")) {
12
+ throw new Error("Tangle modelCredentials accepts only apiKeyEnv and baseUrl");
13
+ }
14
+ const { apiKeyEnv, baseUrl } = value;
15
+ if (typeof apiKeyEnv !== "string" || apiKeyEnv.length > 128 ||
16
+ !/^[A-Z_][A-Z0-9_]*$/.test(apiKeyEnv)) {
17
+ throw new Error("Tangle modelCredentials apiKeyEnv must be a stored secret name");
18
+ }
19
+ let url;
20
+ try {
21
+ url = new URL(baseUrl);
22
+ }
23
+ catch {
24
+ throw new Error("Tangle modelCredentials baseUrl must be an HTTP(S) endpoint");
25
+ }
26
+ if (typeof baseUrl !== "string" || baseUrl.length > 2048 || /\s/.test(baseUrl) ||
27
+ !["http:", "https:"].includes(url.protocol) ||
28
+ url.username || url.password || url.search || url.hash) {
29
+ throw new Error("Tangle modelCredentials baseUrl must be an HTTP(S) endpoint without credentials, query, or fragment");
30
+ }
31
+ return Object.freeze({ apiKeyEnv, baseUrl });
32
+ }
33
+ export function sandboxOptionsFromCreateInput(input, defaultBackend, parsedWorkspace, modelCredentials) {
7
34
  const workspace = assertCreateInputShape(input, parsedWorkspace) ?? {};
8
35
  const profile = inlineAgentProfile(input.profile);
9
36
  const backend = input.backend === undefined
10
37
  ? parseBackendType(defaultBackend ?? profile.harness ?? "opencode")
11
38
  : input.backend;
12
39
  assertNoInlineSecretValues(input, workspace);
40
+ if (modelCredentials !== undefined &&
41
+ (!Array.isArray(input.secrets) || !input.secrets.includes(modelCredentials.apiKeyEnv))) {
42
+ throw new Error("Tangle modelCredentials apiKeyEnv must be explicitly listed in create secrets");
43
+ }
13
44
  if (input.providerOptions && Object.keys(input.providerOptions).length > 0) {
14
45
  throw new Error("Tangle create providerOptions are not supported");
15
46
  }
@@ -71,6 +102,7 @@ export function sandboxOptionsFromCreateInput(input, defaultBackend, parsedWorks
71
102
  ...(base.backend ?? {}),
72
103
  type: backend,
73
104
  profile,
105
+ ...(modelCredentials === undefined ? {} : { model: modelCredentials }),
74
106
  ...(input.runtimeAttachments === undefined ? {} : {
75
107
  runtimeAttachments: tangleRuntimeAttachments(input.runtimeAttachments, profile),
76
108
  }),
@@ -4,12 +4,17 @@ import { createTangleExactProcessProvider, } from "./exact-process.js";
4
4
  import { capabilitiesForClient, defaultTangleSandboxCapabilities, narrowTangleCapabilitiesToBackend, } from "./tangle-capabilities.js";
5
5
  import { sandboxInstanceAsEnvironment } from "./tangle-environment.js";
6
6
  import { confidentialVerifierOption, createTangleWorkspaceBranching, } from "./tangle-workspace-branching.js";
7
- import { assertCreateInputShape, assertMappedCreateOptions, assertMappedSecretNames, assertNoInlineSecretValues, sandboxOptionsFromCreateInput } from "./tangle-create-options.js";
7
+ import { assertCreateInputShape, assertMappedCreateOptions, assertMappedSecretNames, assertNoInlineSecretValues, captureModelCredentials, sandboxOptionsFromCreateInput } from "./tangle-create-options.js";
8
8
  import { statusFromUnknown } from "./tangle-environment-values.js";
9
9
  import { awaitSandboxRunning, DEFAULT_TANGLE_READY_TIMEOUT_MS, } from "./tangle-readiness.js";
10
10
  import { requestedResourceProfile } from "./tangle-resources.js";
11
11
  import { assertBoundedJson, attachCleanupHandle, awaitWithSignal, boundedIdentifier, boundedString, MAX_LIST_RESULTS, SANDBOX_LIST_PAGE_SIZE, } from "./tangle-contract-safety.js";
12
12
  export function createTangleProvider(options) {
13
+ const modelCredentials = captureModelCredentials(options.modelCredentials);
14
+ const mapCreateInput = options.mapCreateInput;
15
+ if (modelCredentials !== undefined && mapCreateInput !== undefined) {
16
+ throw new Error("Tangle modelCredentials cannot be combined with mapCreateInput");
17
+ }
13
18
  const providerName = options.name ?? "tangle-sandbox";
14
19
  boundedIdentifier(providerName, "Tangle provider name");
15
20
  const readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_TANGLE_READY_TIMEOUT_MS;
@@ -57,8 +62,8 @@ export function createTangleProvider(options) {
57
62
  if (input.providerOptions && Object.keys(input.providerOptions).length > 0) {
58
63
  throw new Error("Tangle create providerOptions are not supported");
59
64
  }
60
- const mappedOptions = options.mapCreateInput?.(input) ??
61
- sandboxOptionsFromCreateInput(input, options.defaultBackend, parsedWorkspace);
65
+ const mappedOptions = mapCreateInput?.(input) ??
66
+ sandboxOptionsFromCreateInput(input, options.defaultBackend, parsedWorkspace, modelCredentials);
62
67
  assertMappedCreateOptions(mappedOptions);
63
68
  const createOptions = deepFreeze(structuredClone(mappedOptions));
64
69
  if (input.idempotencyKey !== undefined &&
@@ -586,6 +586,15 @@ export interface TangleProviderOptions {
586
586
  name?: string;
587
587
  /** Overrides an inline profile's harness when create() names no backend. */
588
588
  defaultBackend?: BackendType;
589
+ /**
590
+ * Stored model credential and endpoint used by the default create mapping.
591
+ * Each create must explicitly list apiKeyEnv in its secrets.
592
+ * Cannot be combined with mapCreateInput; never accepts credential values.
593
+ */
594
+ modelCredentials?: {
595
+ apiKeyEnv: string;
596
+ baseUrl: string;
597
+ };
589
598
  capabilities?: AgentEnvironmentCapabilities | (() => AgentEnvironmentCapabilities | Promise<AgentEnvironmentCapabilities>);
590
599
  validateProfile?: AgentEnvironmentProvider["validateProfile"];
591
600
  /** Replaces the default mapping, including its backend and profile selection. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-provider-tangle",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
5
5
  "type": "module",
6
6
  "license": "MIT",