@tangle-network/agent-app 0.44.23 → 0.44.25

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.
@@ -16,6 +16,186 @@ type Outcome<T> = {
16
16
  error: Error;
17
17
  };
18
18
 
19
+ /** Define configuration options for resolving a provider and its model with optional API keys and routing details */
20
+ interface ProviderResolutionConfig {
21
+ routerBaseUrl?: string;
22
+ apiKey?: string;
23
+ providerName?: string;
24
+ modelName?: string;
25
+ defaultModel?: string;
26
+ openaiApiKey?: string;
27
+ allowKeylessModel?: boolean;
28
+ }
29
+ /** Represent a fully configured model with optional API key and base URL for sandbox platform integration */
30
+ interface ResolvedModel {
31
+ model: string;
32
+ provider: string;
33
+ apiKey?: string;
34
+ baseUrl?: string;
35
+ }
36
+ /**
37
+ * Why a model failed to resolve into something transportable to the sandbox
38
+ * platform. `no_provider` — a model id exists but no provider name could be
39
+ * derived (no explicit `providerName`, and no key present to infer one from).
40
+ * `no_api_key` — a provider AND model both resolved, but no credential is
41
+ * configured and the caller did not opt into `allowKeylessModel`.
42
+ */
43
+ type ModelSelectionError = 'no_provider' | 'no_api_key';
44
+ /**
45
+ * Which precedence slot supplied the failed/succeeded model id:
46
+ * `override` — the caller's per-turn `{ model }` argument.
47
+ * `config` — `provider.modelName`.
48
+ * `default` — `provider.defaultModel` (only ever consulted for an
49
+ * openai/openai-compat provider shape).
50
+ */
51
+ type ModelSelectionSource = 'override' | 'config' | 'default';
52
+ /**
53
+ * A model id was named (by override, config, or default) but is not
54
+ * transportable to the sandbox platform. Carries enough to explain WHY
55
+ * without the caller re-deriving the override/config/default precedence
56
+ * chain itself (that re-derivation is how gtm-agent#665 happened — a caller
57
+ * guessed loudness from the wrong slot and dropped a user-selected model).
58
+ */
59
+ type ModelSelectionFailure = {
60
+ succeeded: false;
61
+ error: 'no_provider';
62
+ model: string;
63
+ source: ModelSelectionSource;
64
+ } | {
65
+ succeeded: false;
66
+ error: 'no_api_key';
67
+ model: string;
68
+ provider: string;
69
+ source: ModelSelectionSource;
70
+ };
71
+ /**
72
+ * The three-state outcome of resolving a model: `{ succeeded: true, value:
73
+ * undefined }` means NOTHING was requested (the legitimate box-default
74
+ * configuration — not an error); `{ succeeded: true, value: ResolvedModel }`
75
+ * means a fully transportable model resolved; anything else is a
76
+ * {@link ModelSelectionFailure} — a model WAS named but can't be sent.
77
+ */
78
+ type ModelSelection = {
79
+ succeeded: true;
80
+ value: ResolvedModel | undefined;
81
+ } | ModelSelectionFailure;
82
+ /**
83
+ * Resolve a provider + model configuration into a typed three-state outcome
84
+ * that separates "nothing requested" from "something requested but
85
+ * untransportable" — the distinction {@link resolveModel} collapses and the
86
+ * one that caused gtm-agent#665 (a validated user-selected model silently
87
+ * dropped, box default substituted, durable row still recording the user's
88
+ * choice).
89
+ *
90
+ * Precedence (identical to the legacy `resolveModel`, byte-for-byte):
91
+ * provider is computed first (`providerName`, else inferred `openai-compat`
92
+ * from a present key, else inferred `openai` from a present
93
+ * `openaiApiKey`, else unresolved); the model id is `override.model` else
94
+ * `config.modelName` else — ONLY when the provider is `openai` or
95
+ * `openai-compat` — `config.defaultModel`; the api key is
96
+ * `override.modelApiKey` else `config.apiKey` else — only for provider
97
+ * `openai` — `config.openaiApiKey`.
98
+ *
99
+ * Two behavioral deltas from the legacy function:
100
+ * 1. Every string field (`routerBaseUrl`, `apiKey`, `providerName`,
101
+ * `modelName`, `defaultModel`, `openaiApiKey`, `override.model`,
102
+ * `override.modelApiKey`) is normalized through {@link trimOrNull} first,
103
+ * so `''` (and whitespace-only strings) are treated as absent instead of
104
+ * poisoning the `??` precedence chain — the issue's second stated defect.
105
+ * This also means a value is trimmed (`' gpt-5 '` resolves to `'gpt-5'`).
106
+ * 2. The outcome is three-state instead of collapsing to `undefined`: no
107
+ * model derivable from any slot → `{ succeeded: true, value: undefined }`
108
+ * (still the legitimate "let the box pick its own default" case, NOT an
109
+ * error); a model id resolved but no provider could be derived → a
110
+ * `no_provider` failure; a model + provider resolved but no api key (and
111
+ * `allowKeylessModel` was not set) → a `no_api_key` failure. Both failure
112
+ * arms carry `source` so a caller can apply a loudness policy without
113
+ * re-deriving which precedence slot supplied the model.
114
+ */
115
+ declare function resolveModelSelection(config: ProviderResolutionConfig | undefined, override?: {
116
+ model?: string;
117
+ modelApiKey?: string;
118
+ }): ModelSelection;
119
+ /**
120
+ * Resolve and return the appropriate model configuration based on provider
121
+ * settings and optional overrides.
122
+ *
123
+ * Migration note (intentionally NOT tagged `@deprecated`): this is a thin,
124
+ * source-compatible wrapper over {@link resolveModelSelection} that collapses
125
+ * its three-state outcome back down to `ResolvedModel | undefined`, exactly
126
+ * as before. It stays correct for the common case (no explicit model requested, or a
127
+ * fully-transportable one), but it CANNOT distinguish "nothing was
128
+ * requested" from "a named model could not be transported" — the ambiguity
129
+ * that caused gtm-agent#665. Prefer {@link resolveModelSelection} directly,
130
+ * or {@link requireTransportableModel} for the fail-loud-on-explicit-model
131
+ * policy the internal sandbox callers use. Not tagged `@deprecated`: it
132
+ * remains the correct call for a caller that never sets an explicit
133
+ * override and only wants "the box's default is fine" semantics; a hard
134
+ * deprecation is a later-major decision, not this fix's.
135
+ *
136
+ * The only behavior change on this entry point versus before is the
137
+ * empty-string bugfix that comes free through delegation (issue #302's
138
+ * second defect) — every signature and the `undefined` return semantics are
139
+ * unchanged.
140
+ */
141
+ declare function resolveModel(config: ProviderResolutionConfig | undefined, override?: {
142
+ model?: string;
143
+ modelApiKey?: string;
144
+ }): ResolvedModel | undefined;
145
+ /**
146
+ * Thrown by {@link requireTransportableModel} when a model that was
147
+ * EXPLICITLY requested via a per-turn `{ model }` override cannot be
148
+ * transported to the sandbox platform. The message names the model, which
149
+ * precedence slot supplied it, what's missing, and the fix, and states
150
+ * plainly that the requested model was NOT sent to the box (the failure mode
151
+ * this error exists to make impossible to miss — gtm-agent#665 silently
152
+ * substituted the box default instead of a user-selected model).
153
+ *
154
+ * The error class itself carries no opinion about *when* it should be
155
+ * thrown — it is constructed from any {@link ModelSelectionFailure},
156
+ * regardless of `source`. A caller wanting a stricter policy (e.g. also
157
+ * fail-loud on an untransportable configured `provider.modelName`) can call
158
+ * {@link resolveModelSelection} directly and throw this itself.
159
+ */
160
+ declare class SandboxModelResolutionError extends Error {
161
+ readonly code: ModelSelectionError;
162
+ readonly model: string;
163
+ readonly provider?: string;
164
+ readonly source: ModelSelectionSource;
165
+ constructor(failure: ModelSelectionFailure, context: string);
166
+ }
167
+ /**
168
+ * Shared fail-loud policy for the sandbox platform's three internal model
169
+ * callers (`ensureWorkspaceSandbox`'s `backendModelAtCreate`,
170
+ * `streamSandboxPrompt`, `driveSandboxTurn`): a `ModelSelection` in, a plain
171
+ * `ResolvedModel | undefined` out, so downstream code is unchanged from
172
+ * before this fix.
173
+ *
174
+ * The policy: success delegates straight through. A failure whose `source`
175
+ * is `'override'` means a PER-TURN model was explicitly selected THIS turn
176
+ * (a live, user-driven choice — passed as `{ model }`) and could not be
177
+ * sent; substituting the box default there is exactly the gtm-agent#665
178
+ * defect, so it throws {@link SandboxModelResolutionError} rather than
179
+ * silently falling back.
180
+ *
181
+ * A failure whose `source` is `'config'` or `'default'` means a
182
+ * *configured* `provider.modelName` / `provider.defaultModel` couldn't
183
+ * resolve — nobody made a choice this turn; the value came from board
184
+ * config that may simply describe "the platform supplies the credential."
185
+ * Shipped consumers rely on exactly that: tax-agent ships a shell with
186
+ * `provider: { providerName: 'openai-compat', modelName, routerBaseUrl }`
187
+ * and no `apiKey`/`allowKeylessModel`, with a contract test asserting
188
+ * `ensureWorkspaceSandbox` creation SUCCEEDS with the model silently
189
+ * dropped so the sandbox platform mints its own in-container credential
190
+ * (`apps/web/tests/sandbox-service-contract.test.ts`). A config-loud policy
191
+ * here would break every fresh tax sandbox provisioning and every tax turn.
192
+ * So both `'config'` and `'default'` keep the pre-#302 logged-skip
193
+ * behavior: `console.error` and drop, letting the box use its own default.
194
+ * A product wanting strict enforcement of a configured `provider.modelName`
195
+ * can call {@link resolveModelSelection} directly and apply its own policy.
196
+ */
197
+ declare function requireTransportableModel(selection: ModelSelection, context: string): ResolvedModel | undefined;
198
+
19
199
  /**
20
200
  * Reading arbitrary bytes out of a sandbox over an exec channel that only
21
201
  * speaks text.
@@ -358,16 +538,6 @@ interface SandboxResourceConfig {
358
538
  maxLifetimeSeconds: number;
359
539
  idleTimeoutSeconds: number;
360
540
  }
361
- /** Define configuration options for resolving a provider and its model with optional API keys and routing details */
362
- interface ProviderResolutionConfig {
363
- routerBaseUrl?: string;
364
- apiKey?: string;
365
- providerName?: string;
366
- modelName?: string;
367
- defaultModel?: string;
368
- openaiApiKey?: string;
369
- allowKeylessModel?: boolean;
370
- }
371
541
  /** Define the context for building a sandbox including workspace, integrations, and optional user ID */
372
542
  interface SandboxBuildContext {
373
543
  workspaceId: string;
@@ -521,6 +691,23 @@ type ExistingBoxStage = 'reused' | 'resumed';
521
691
  declare class SandboxRuntimeAuthRefreshError extends Error {
522
692
  constructor(stage: ExistingBoxStage, name: string, detail: string, cause?: unknown);
523
693
  }
694
+ /** Which step of the state-preserving stop→resume recovery failed. `stop` with a
695
+ * driver-unsupported cause means the platform cannot restart this box (the
696
+ * `tangle` driver exposes create/delete only); `probe` means the box restarted
697
+ * but is still unresponsive. */
698
+ type SandboxRecoveryPhase = 'stop' | 'resume' | 'probe';
699
+ /**
700
+ * Thrown when an unresponsive box could not be recovered by a state-preserving
701
+ * restart. Contract: this error is only ever thrown with the workspace intact —
702
+ * recovery never deletes. The caller decides what to do next (retry, surface to
703
+ * the user, or explicitly replace via `forceNew`).
704
+ */
705
+ declare class SandboxRecoveryFailedError extends Error {
706
+ readonly boxKey: string;
707
+ readonly stage: ExistingBoxStage;
708
+ readonly phase: SandboxRecoveryPhase;
709
+ constructor(stage: ExistingBoxStage, boxKey: string, phase: SandboxRecoveryPhase, detail: string, cause?: unknown);
710
+ }
524
711
  /** Define options to control execution timeout, pacing, and retry behavior when writing profile files */
525
712
  interface WriteProfileFilesOptions {
526
713
  execTimeoutMs?: number;
@@ -629,18 +816,6 @@ declare function peekWorkspaceSandbox(shell: SandboxRuntimeConfig, options: {
629
816
  }): Promise<PeekWorkspaceSandboxOutcome>;
630
817
  /** Resolve or create a workspace sandbox instance with optional reuse and progress tracking */
631
818
  declare function ensureWorkspaceSandbox(shell: SandboxRuntimeConfig, options: EnsureWorkspaceSandboxOptions): Promise<SandboxInstance>;
632
- /** Represent a fully configured model with optional API key and base URL for sandbox platform integration */
633
- interface ResolvedModel {
634
- model: string;
635
- provider: string;
636
- apiKey?: string;
637
- baseUrl?: string;
638
- }
639
- /** Resolve and return the appropriate model configuration based on provider settings and optional overrides */
640
- declare function resolveModel(config: ProviderResolutionConfig | undefined, override?: {
641
- model?: string;
642
- modelApiKey?: string;
643
- }): ResolvedModel | undefined;
644
819
  /** Extract a single element type from the array parameter of SandboxInstance's streamPrompt method */
645
820
  type PromptInputPart = Extract<Parameters<SandboxInstance['streamPrompt']>[0], readonly unknown[]>[number];
646
821
  /** Build a single string combining conversation history and the current user message */
@@ -801,4 +976,4 @@ declare function isTerminalPromptEvent(event: unknown): boolean;
801
976
  /** Resolve the interactive question text from a structured event or return null if none found */
802
977
  declare function detectInteractiveQuestion(event: unknown): string | null;
803
978
 
804
- export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, DEFAULT_SANDBOX_RESOURCES, type DriveSandboxTurnOptions, ENV_TOTAL_MAX_BYTES, ENV_VALUE_MAX_BYTES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type Outcome, PROVISION_PAYLOAD_MAX_BYTES, type PeekWorkspaceSandboxOutcome, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ProvisionPayloadSections, type ProvisionProfileSection, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, type SandboxExecChannel, type SandboxExecOptions, type SandboxFileBytesOutcome, type SandboxFileSizeOutcome, type SandboxPermissionLevel, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, type WriteProfileFilesOptions, assertEnvWithinLimits, assertProvisionPayloadWithinCap, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, collectSandboxPromptText, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deferredCorpusHash, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, mintTerminalProxyToken, peekWorkspaceSandbox, readSandboxBinaryBytes, readSecret, resetClientCache, resolveModel, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, shellQuote, splitDeferredProfileFiles, statSandboxFileSize, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox };
979
+ export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, DEFAULT_SANDBOX_RESOURCES, type DriveSandboxTurnOptions, ENV_TOTAL_MAX_BYTES, ENV_VALUE_MAX_BYTES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type ModelSelection, type ModelSelectionError, type ModelSelectionFailure, type ModelSelectionSource, type Outcome, PROVISION_PAYLOAD_MAX_BYTES, type PeekWorkspaceSandboxOutcome, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ProvisionPayloadSections, type ProvisionProfileSection, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, type SandboxExecChannel, type SandboxExecOptions, type SandboxFileBytesOutcome, type SandboxFileSizeOutcome, SandboxModelResolutionError, type SandboxPermissionLevel, SandboxRecoveryFailedError, type SandboxRecoveryPhase, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, type WriteProfileFilesOptions, assertEnvWithinLimits, assertProvisionPayloadWithinCap, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, collectSandboxPromptText, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deferredCorpusHash, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, mintTerminalProxyToken, peekWorkspaceSandbox, readSandboxBinaryBytes, readSecret, requireTransportableModel, resetClientCache, resolveModel, resolveModelSelection, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, shellQuote, splitDeferredProfileFiles, statSandboxFileSize, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox };
@@ -3,6 +3,8 @@ import {
3
3
  ENV_TOTAL_MAX_BYTES,
4
4
  ENV_VALUE_MAX_BYTES,
5
5
  PROVISION_PAYLOAD_MAX_BYTES,
6
+ SandboxModelResolutionError,
7
+ SandboxRecoveryFailedError,
6
8
  SandboxRuntimeAuthRefreshError,
7
9
  assertEnvWithinLimits,
8
10
  assertProvisionPayloadWithinCap,
@@ -38,8 +40,10 @@ import {
38
40
  peekWorkspaceSandbox,
39
41
  readSandboxBinaryBytes,
40
42
  readSecret,
43
+ requireTransportableModel,
41
44
  resetClientCache,
42
45
  resolveModel,
46
+ resolveModelSelection,
43
47
  resolveSandboxClientCredentials,
44
48
  runSandboxPrompt,
45
49
  runSandboxToolPathSetup,
@@ -59,7 +63,7 @@ import {
59
63
  verifySandboxTerminalToken,
60
64
  verifyTerminalProxyToken,
61
65
  writeProfileFilesToBox
62
- } from "../chunk-2ZSSOYXP.js";
66
+ } from "../chunk-Q74BS43G.js";
63
67
  import "../chunk-LWSJK546.js";
64
68
  import "../chunk-CQZSAR77.js";
65
69
  import "../chunk-ICOHEZK6.js";
@@ -72,6 +76,8 @@ export {
72
76
  ENV_TOTAL_MAX_BYTES,
73
77
  ENV_VALUE_MAX_BYTES,
74
78
  PROVISION_PAYLOAD_MAX_BYTES,
79
+ SandboxModelResolutionError,
80
+ SandboxRecoveryFailedError,
75
81
  SandboxRuntimeAuthRefreshError,
76
82
  assertEnvWithinLimits,
77
83
  assertProvisionPayloadWithinCap,
@@ -107,8 +113,10 @@ export {
107
113
  peekWorkspaceSandbox,
108
114
  readSandboxBinaryBytes,
109
115
  readSecret,
116
+ requireTransportableModel,
110
117
  resetClientCache,
111
118
  resolveModel,
119
+ resolveModelSelection,
112
120
  resolveSandboxClientCredentials,
113
121
  runSandboxPrompt,
114
122
  runSandboxToolPathSetup,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.44.23",
3
+ "version": "0.44.25",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [