deepline 0.1.317 → 0.1.319

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.
@@ -1127,6 +1127,13 @@ function isStagedUploadMintUnsupported(error: unknown): boolean {
1127
1127
  return error instanceof DeeplineError && error.statusCode === 404;
1128
1128
  }
1129
1129
 
1130
+ function isStagedUploadDirectEgressDenied(error: unknown): boolean {
1131
+ return (
1132
+ error instanceof DeeplineError &&
1133
+ error.code === 'STAGED_FILE_UPLOAD_EGRESS_DENIED'
1134
+ );
1135
+ }
1136
+
1130
1137
  function formatMegabytes(bytes: number): string {
1131
1138
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1132
1139
  }
@@ -1159,10 +1166,16 @@ async function uploadStagedFileToPresignedUrl(input: {
1159
1166
  return;
1160
1167
  }
1161
1168
  const text = await response.text().catch(() => '');
1169
+ const egressDenied =
1170
+ response.status === 403 &&
1171
+ response.headers.get('x-deny-reason')?.trim().toLowerCase() ===
1172
+ 'host_not_allowed';
1162
1173
  lastError = new DeeplineError(
1163
1174
  `Direct storage upload of ${input.logicalPath} failed: ${response.status} ${text.slice(0, 200)}`,
1164
1175
  response.status,
1165
- 'STAGED_FILE_UPLOAD_FAILED',
1176
+ egressDenied
1177
+ ? 'STAGED_FILE_UPLOAD_EGRESS_DENIED'
1178
+ : 'STAGED_FILE_UPLOAD_FAILED',
1166
1179
  { logicalPath: input.logicalPath },
1167
1180
  );
1168
1181
  // 4xx (other than throttling) will not recover on retry.
@@ -2502,7 +2515,10 @@ export class DeeplineClient {
2502
2515
  );
2503
2516
  }
2504
2517
 
2505
- await Promise.all(
2518
+ type DirectStageResult =
2519
+ | { ref: PlayStagedFileRef }
2520
+ | { fallbackFile: (typeof files)[number] };
2521
+ const directResults: DirectStageResult[] = await Promise.all(
2506
2522
  files.map(async (file) => {
2507
2523
  const upload = uploadByIdentity.get(
2508
2524
  stagedUploadIdentity(file.logicalPath, file.contentHash),
@@ -2515,32 +2531,46 @@ export class DeeplineClient {
2515
2531
  );
2516
2532
  }
2517
2533
  if (upload.alreadyStaged || !upload.uploadUrl) {
2518
- return;
2534
+ return { ref: upload.ref };
2535
+ }
2536
+ try {
2537
+ await uploadStagedFileToPresignedUrl({
2538
+ url: upload.uploadUrl,
2539
+ headers: upload.uploadHeaders ?? {
2540
+ 'content-type': file.contentType,
2541
+ },
2542
+ body: decodeBase64Bytes(file.contentBase64),
2543
+ logicalPath: file.logicalPath,
2544
+ });
2545
+ return { ref: upload.ref };
2546
+ } catch (error) {
2547
+ if (isStagedUploadDirectEgressDenied(error)) {
2548
+ return { fallbackFile: file };
2549
+ }
2550
+ throw error;
2519
2551
  }
2520
- await uploadStagedFileToPresignedUrl({
2521
- url: upload.uploadUrl,
2522
- headers: upload.uploadHeaders ?? {
2523
- 'content-type': file.contentType,
2524
- },
2525
- body: decodeBase64Bytes(file.contentBase64),
2526
- logicalPath: file.logicalPath,
2527
- });
2528
2552
  }),
2529
2553
  );
2530
2554
 
2531
- return files.map((file) => {
2532
- const upload = uploadByIdentity.get(
2533
- stagedUploadIdentity(file.logicalPath, file.contentHash),
2534
- );
2535
- if (!upload) {
2536
- throw new DeeplineError(
2537
- `The staging server did not return an upload target for ${file.logicalPath}.`,
2538
- undefined,
2539
- 'STAGED_FILE_MINT_INCOMPLETE',
2540
- );
2541
- }
2542
- return upload.ref;
2543
- });
2555
+ return await Promise.all(
2556
+ directResults.map(async (result) => {
2557
+ if ('fallbackFile' in result) {
2558
+ const [ref] = await this.stagePlayFilesViaMultipart(
2559
+ [result.fallbackFile],
2560
+ 'direct-egress-denied',
2561
+ );
2562
+ if (!ref) {
2563
+ throw new DeeplineError(
2564
+ `The staging server did not return an upload target for ${result.fallbackFile.logicalPath}.`,
2565
+ undefined,
2566
+ 'STAGED_FILE_MINT_INCOMPLETE',
2567
+ );
2568
+ }
2569
+ return ref;
2570
+ }
2571
+ return result.ref;
2572
+ }),
2573
+ );
2544
2574
  }
2545
2575
 
2546
2576
  /**
@@ -2572,11 +2602,16 @@ export class DeeplineClient {
2572
2602
  contentType: string;
2573
2603
  bytes: number;
2574
2604
  }>,
2605
+ reason: 'mint-unsupported' | 'direct-egress-denied' = 'mint-unsupported',
2575
2606
  ): Promise<PlayStagedFileRef[]> {
2576
2607
  for (const file of files) {
2577
2608
  if (file.bytes > STAGE_LEGACY_MULTIPART_MAX_BYTES) {
2609
+ const reasonMessage =
2610
+ reason === 'direct-egress-denied'
2611
+ ? "direct storage is blocked by this environment's network egress policy"
2612
+ : 'the connected Deepline server does not support direct-to-storage uploads';
2578
2613
  throw new DeeplineError(
2579
- `Cannot stage ${file.logicalPath} (${formatMegabytes(file.bytes)}): the connected Deepline server does not support direct-to-storage uploads, and this file exceeds the ~4.5MB request-body limit the legacy upload path is subject to. Update the Deepline server (or target code.deepline.com) to stage files this large.`,
2614
+ `Cannot stage ${file.logicalPath} (${formatMegabytes(file.bytes)}): ${reasonMessage}, and this file exceeds the ~4.5MB request-body limit the legacy upload path is subject to.`,
2580
2615
  413,
2581
2616
  'STAGED_FILE_TOO_LARGE_FOR_LEGACY_UPLOAD',
2582
2617
  {
@@ -157,7 +157,7 @@ export const SDK_RELEASE = {
157
157
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
158
158
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
159
159
  // Operators use the checkout-local deepline-admin binary instead.
160
- version: '0.1.317',
160
+ version: '0.1.319',
161
161
  contracts: {
162
162
  api: {
163
163
  name: 'sdk-http-api',
@@ -276,16 +276,30 @@ export interface ToolDefinition {
276
276
  }>;
277
277
  /**
278
278
  * Whether this tool is callable in the current workspace. `false` for a
279
- * bring-your-own-credential provider (e.g. Apollo) that has not been
280
- * connected — the agent should offer to connect it rather than call it.
279
+ * bring-your-own-credential provider that has not been connected.
281
280
  */
282
281
  connected?: boolean;
282
+ /** Whether the tool can be executed. Exact lookup may return non-callable deprecated aliases. */
283
+ callable?: boolean;
284
+ /** True when callers should migrate this exact tool id to its replacement. */
285
+ deprecated?: boolean;
286
+ /** Deprecation reason, replacement, and compatibility execution behavior. */
287
+ deprecation?: {
288
+ replacementToolId: string;
289
+ message: string;
290
+ execution?: 'terminal' | 'forward';
291
+ };
283
292
  /**
284
293
  * Connection status for discovery: `managed` (Deepline-run credentials),
285
294
  * `connected` (your own credential is connected), or `requires_connection`
286
- * (BYO provider not yet connected in this workspace).
295
+ * (BYO provider not yet connected in this workspace). `deprecated` means
296
+ * connecting credentials will not make the tool callable.
287
297
  */
288
- credentialStatus?: 'managed' | 'connected' | 'requires_connection';
298
+ credentialStatus?:
299
+ | 'managed'
300
+ | 'connected'
301
+ | 'requires_connection'
302
+ | 'deprecated';
289
303
  /** True when the tool requires a customer-provided credential to run. */
290
304
  requiresOwnCredential?: boolean;
291
305
  /** Actionable message shown when a connection is required. */
package/dist/cli/index.js CHANGED
@@ -1037,7 +1037,7 @@ var SDK_RELEASE = {
1037
1037
  // 0.1.253 makes play-page browser opening opt-in and retires --no-open.
1038
1038
  // 0.1.254 removes the internal operations tree from the published SDK CLI.
1039
1039
  // Operators use the checkout-local deepline-admin binary instead.
1040
- version: "0.1.317",
1040
+ version: "0.1.319",
1041
1041
  contracts: {
1042
1042
  api: {
1043
1043
  name: "sdk-http-api",
@@ -3527,6 +3527,9 @@ function stagedUploadIdentity(logicalPath, contentHash) {
3527
3527
  function isStagedUploadMintUnsupported(error) {
3528
3528
  return error instanceof DeeplineError && error.statusCode === 404;
3529
3529
  }
3530
+ function isStagedUploadDirectEgressDenied(error) {
3531
+ return error instanceof DeeplineError && error.code === "STAGED_FILE_UPLOAD_EGRESS_DENIED";
3532
+ }
3530
3533
  function formatMegabytes(bytes) {
3531
3534
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
3532
3535
  }
@@ -3547,10 +3550,11 @@ async function uploadStagedFileToPresignedUrl(input2) {
3547
3550
  return;
3548
3551
  }
3549
3552
  const text = await response.text().catch(() => "");
3553
+ const egressDenied = response.status === 403 && response.headers.get("x-deny-reason")?.trim().toLowerCase() === "host_not_allowed";
3550
3554
  lastError = new DeeplineError(
3551
3555
  `Direct storage upload of ${input2.logicalPath} failed: ${response.status} ${text.slice(0, 200)}`,
3552
3556
  response.status,
3553
- "STAGED_FILE_UPLOAD_FAILED",
3557
+ egressDenied ? "STAGED_FILE_UPLOAD_EGRESS_DENIED" : "STAGED_FILE_UPLOAD_FAILED",
3554
3558
  { logicalPath: input2.logicalPath }
3555
3559
  );
3556
3560
  if (response.status < 500 && response.status !== 429) {
@@ -4450,7 +4454,7 @@ var DeeplineClient = class {
4450
4454
  upload
4451
4455
  );
4452
4456
  }
4453
- await Promise.all(
4457
+ const directResults = await Promise.all(
4454
4458
  files.map(async (file) => {
4455
4459
  const upload = uploadByIdentity.get(
4456
4460
  stagedUploadIdentity(file.logicalPath, file.contentHash)
@@ -4463,31 +4467,45 @@ var DeeplineClient = class {
4463
4467
  );
4464
4468
  }
4465
4469
  if (upload.alreadyStaged || !upload.uploadUrl) {
4466
- return;
4470
+ return { ref: upload.ref };
4471
+ }
4472
+ try {
4473
+ await uploadStagedFileToPresignedUrl({
4474
+ url: upload.uploadUrl,
4475
+ headers: upload.uploadHeaders ?? {
4476
+ "content-type": file.contentType
4477
+ },
4478
+ body: decodeBase64Bytes(file.contentBase64),
4479
+ logicalPath: file.logicalPath
4480
+ });
4481
+ return { ref: upload.ref };
4482
+ } catch (error) {
4483
+ if (isStagedUploadDirectEgressDenied(error)) {
4484
+ return { fallbackFile: file };
4485
+ }
4486
+ throw error;
4467
4487
  }
4468
- await uploadStagedFileToPresignedUrl({
4469
- url: upload.uploadUrl,
4470
- headers: upload.uploadHeaders ?? {
4471
- "content-type": file.contentType
4472
- },
4473
- body: decodeBase64Bytes(file.contentBase64),
4474
- logicalPath: file.logicalPath
4475
- });
4476
4488
  })
4477
4489
  );
4478
- return files.map((file) => {
4479
- const upload = uploadByIdentity.get(
4480
- stagedUploadIdentity(file.logicalPath, file.contentHash)
4481
- );
4482
- if (!upload) {
4483
- throw new DeeplineError(
4484
- `The staging server did not return an upload target for ${file.logicalPath}.`,
4485
- void 0,
4486
- "STAGED_FILE_MINT_INCOMPLETE"
4487
- );
4488
- }
4489
- return upload.ref;
4490
- });
4490
+ return await Promise.all(
4491
+ directResults.map(async (result) => {
4492
+ if ("fallbackFile" in result) {
4493
+ const [ref] = await this.stagePlayFilesViaMultipart(
4494
+ [result.fallbackFile],
4495
+ "direct-egress-denied"
4496
+ );
4497
+ if (!ref) {
4498
+ throw new DeeplineError(
4499
+ `The staging server did not return an upload target for ${result.fallbackFile.logicalPath}.`,
4500
+ void 0,
4501
+ "STAGED_FILE_MINT_INCOMPLETE"
4502
+ );
4503
+ }
4504
+ return ref;
4505
+ }
4506
+ return result.ref;
4507
+ })
4508
+ );
4491
4509
  }
4492
4510
  /**
4493
4511
  * Mint short-lived presigned upload targets for staged play files.
@@ -4500,11 +4518,12 @@ var DeeplineClient = class {
4500
4518
  const response = await this.http.post("/api/v2/plays/files/stage/mint", { files });
4501
4519
  return response.uploads ?? [];
4502
4520
  }
4503
- async stagePlayFilesViaMultipart(files) {
4521
+ async stagePlayFilesViaMultipart(files, reason = "mint-unsupported") {
4504
4522
  for (const file of files) {
4505
4523
  if (file.bytes > STAGE_LEGACY_MULTIPART_MAX_BYTES) {
4524
+ const reasonMessage = reason === "direct-egress-denied" ? "direct storage is blocked by this environment's network egress policy" : "the connected Deepline server does not support direct-to-storage uploads";
4506
4525
  throw new DeeplineError(
4507
- `Cannot stage ${file.logicalPath} (${formatMegabytes(file.bytes)}): the connected Deepline server does not support direct-to-storage uploads, and this file exceeds the ~4.5MB request-body limit the legacy upload path is subject to. Update the Deepline server (or target code.deepline.com) to stage files this large.`,
4526
+ `Cannot stage ${file.logicalPath} (${formatMegabytes(file.bytes)}): ${reasonMessage}, and this file exceeds the ~4.5MB request-body limit the legacy upload path is subject to.`,
4508
4527
  413,
4509
4528
  "STAGED_FILE_TOO_LARGE_FOR_LEGACY_UPLOAD",
4510
4529
  {
@@ -28570,14 +28589,23 @@ function toolContractJsonForDescribe(tool, requestedToolId) {
28570
28589
  "deeplineUsdPerPricingUnit",
28571
28590
  "deepline_usd_per_pricing_unit"
28572
28591
  );
28573
- const starterScript = !isPlayLikeTool(tool) && extractedLists.length > 0 ? starterScriptJson(
28592
+ const deprecation = recordField2(tool, "deprecation");
28593
+ const replacementToolId = stringField2(
28594
+ deprecation,
28595
+ "replacementToolId",
28596
+ "replacement_tool_id"
28597
+ );
28598
+ const deprecationMessage = stringField2(deprecation, "message");
28599
+ const deprecationExecution = stringField2(deprecation, "execution");
28600
+ const deprecated = tool.deprecated === true || stringField2(tool, "credentialStatus", "credential_status") === "deprecated";
28601
+ const starterScript = !deprecated && !isPlayLikeTool(tool) && extractedLists.length > 0 ? starterScriptJson(
28574
28602
  seedToolListScript({
28575
28603
  toolId,
28576
28604
  payload: samplePayloadForInputFields(inputFields),
28577
28605
  rows: []
28578
28606
  })
28579
28607
  ) : null;
28580
- const executeCommand = isPlayLikeTool(tool) ? playRunCommandForTool(tool, toolId) : `deepline tools execute ${toolId} --input '{...}' --json`;
28608
+ const executeCommand = deprecated && replacementToolId ? `deepline tools execute ${replacementToolId} --input '{...}' --json` : isPlayLikeTool(tool) ? playRunCommandForTool(tool, toolId) : `deepline tools execute ${toolId} --input '{...}' --json`;
28581
28609
  return {
28582
28610
  schemaVersion: 1,
28583
28611
  toolId,
@@ -28585,6 +28613,15 @@ function toolContractJsonForDescribe(tool, requestedToolId) {
28585
28613
  displayName: tool.displayName,
28586
28614
  description: tool.description,
28587
28615
  categories: tool.categories,
28616
+ ...deprecated ? {
28617
+ deprecated: true,
28618
+ callable: tool.callable !== false,
28619
+ deprecation: {
28620
+ replacementToolId,
28621
+ message: deprecationMessage,
28622
+ ...deprecationExecution ? { execution: deprecationExecution } : {}
28623
+ }
28624
+ } : {},
28588
28625
  inputFields: inputFields.map((field) => ({
28589
28626
  name: field.name,
28590
28627
  type: field.type ?? "unknown",
@@ -28727,6 +28764,29 @@ function printCompactToolContract(tool, requestedToolId) {
28727
28764
  if (Array.isArray(contract.categories) && contract.categories.length) {
28728
28765
  console.log(`Tags: ${contract.categories.join(", ")}`);
28729
28766
  }
28767
+ if (contract.deprecated === true) {
28768
+ const deprecation = isRecord11(contract.deprecation) ? contract.deprecation : {};
28769
+ const message = stringField2(deprecation, "message");
28770
+ const replacementToolId = stringField2(
28771
+ deprecation,
28772
+ "replacementToolId",
28773
+ "replacement_tool_id"
28774
+ );
28775
+ console.log(
28776
+ contract.callable === false ? "Status: deprecated \u2014 this tool cannot be executed" : "Status: deprecated \u2014 legacy executions forward for compatibility"
28777
+ );
28778
+ if (message) console.log(`Migration: ${message}`);
28779
+ if (replacementToolId) {
28780
+ console.log(
28781
+ `Use: deepline tools execute ${replacementToolId} --input '{...}'`
28782
+ );
28783
+ }
28784
+ console.log("");
28785
+ console.log(
28786
+ `More: deepline tools describe ${replacementToolId || contract.toolId} --json`
28787
+ );
28788
+ return;
28789
+ }
28730
28790
  printToolPricingOnly(tool, requestedToolId, { heading: "Cost" });
28731
28791
  if (inputFields.length) {
28732
28792
  console.log("");
@@ -29226,6 +29286,32 @@ function samplePayload(samples, key) {
29226
29286
  function commandEnvelopeFromRawResponse(rawResponse) {
29227
29287
  return isRecord11(rawResponse) ? { ...rawResponse } : { status: "completed", result: rawResponse };
29228
29288
  }
29289
+ function extractToolExecutionWarningMessages(rawResponse) {
29290
+ if (!isRecord11(rawResponse)) return [];
29291
+ const candidates = [
29292
+ recordField2(recordField2(rawResponse, "toolResponse"), "meta"),
29293
+ recordField2(
29294
+ recordField2(recordField2(rawResponse, "toolResponse"), "raw"),
29295
+ "meta"
29296
+ ),
29297
+ recordField2(rawResponse, "meta"),
29298
+ recordField2(recordField2(rawResponse, "result"), "meta")
29299
+ ];
29300
+ const messages = [];
29301
+ for (const candidate of candidates) {
29302
+ const warnings = candidate?.warnings;
29303
+ if (!Array.isArray(warnings)) continue;
29304
+ for (const warning of warnings) {
29305
+ if (typeof warning === "string" && warning.trim()) {
29306
+ messages.push(warning.trim());
29307
+ } else if (isRecord11(warning)) {
29308
+ const message = stringField2(warning, "message");
29309
+ if (message) messages.push(message);
29310
+ }
29311
+ }
29312
+ }
29313
+ return [...new Set(messages)];
29314
+ }
29229
29315
  function apifySyncRecoveryNext(rawResponse) {
29230
29316
  if (!isRecord11(rawResponse) || rawResponse.status !== "running") return null;
29231
29317
  const toolResponse = recordField2(rawResponse, "toolResponse");
@@ -29493,6 +29579,9 @@ export default definePlay(${JSON.stringify(playName)}, async (ctx) => {
29493
29579
  }
29494
29580
  function buildToolExecuteBaseEnvelope(input2) {
29495
29581
  const envelope = commandEnvelopeFromRawResponse(input2.rawResponse);
29582
+ const warningMessages = extractToolExecutionWarningMessages(
29583
+ input2.rawResponse
29584
+ );
29496
29585
  const apifyRecovery = apifySyncRecoveryNext(input2.rawResponse);
29497
29586
  const summaryEntries = Object.entries(input2.summary);
29498
29587
  const outputPreview = input2.listConversion ? {
@@ -29530,6 +29619,7 @@ function buildToolExecuteBaseEnvelope(input2) {
29530
29619
  ...envelope,
29531
29620
  ...envelopeHasCanonicalOutput || envelopeHasDeclaredOutput ? { output_preview: outputPreview } : { output: outputPreview },
29532
29621
  ...summaryEntries.length > 0 ? { summary: input2.summary } : {},
29622
+ ...warningMessages.length > 0 ? { warnings: warningMessages } : {},
29533
29623
  next: {
29534
29624
  inspect: inspectCommand,
29535
29625
  ...apifyRecovery ?? {},
@@ -29541,6 +29631,7 @@ function buildToolExecuteBaseEnvelope(input2) {
29541
29631
  },
29542
29632
  render: {
29543
29633
  sections: input2.listConversion ? [
29634
+ ...warningMessages.length > 0 ? [{ title: "warnings", lines: warningMessages }] : [],
29544
29635
  {
29545
29636
  title: "output",
29546
29637
  lines: [
@@ -29551,6 +29642,7 @@ function buildToolExecuteBaseEnvelope(input2) {
29551
29642
  ]
29552
29643
  }
29553
29644
  ] : [
29645
+ ...warningMessages.length > 0 ? [{ title: "warnings", lines: warningMessages }] : [],
29554
29646
  {
29555
29647
  title: "result",
29556
29648
  lines: apifyRecovery ? [