deepline 0.1.12 → 0.1.20

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 (82) hide show
  1. package/README.md +14 -6
  2. package/dist/cli/index.js +1346 -717
  3. package/dist/cli/index.mjs +1342 -713
  4. package/dist/index.d.mts +199 -23
  5. package/dist/index.d.ts +199 -23
  6. package/dist/index.js +221 -14
  7. package/dist/index.mjs +221 -14
  8. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +214 -77
  9. package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +85 -60
  10. package/dist/repo/apps/play-runner-workers/src/entry.ts +385 -66
  11. package/dist/repo/sdk/src/client.ts +237 -0
  12. package/dist/repo/sdk/src/config.ts +125 -8
  13. package/dist/repo/sdk/src/http.ts +29 -5
  14. package/dist/repo/sdk/src/play.ts +19 -36
  15. package/dist/repo/sdk/src/plays/bundle-play-file.ts +22 -8
  16. package/dist/repo/sdk/src/plays/local-file-discovery.ts +207 -160
  17. package/dist/repo/sdk/src/types.ts +25 -0
  18. package/dist/repo/sdk/src/version.ts +2 -2
  19. package/dist/repo/shared_libs/play-runtime/tool-result.ts +237 -145
  20. package/dist/repo/shared_libs/plays/bundling/index.ts +206 -229
  21. package/dist/repo/shared_libs/plays/dataset.ts +28 -0
  22. package/dist/repo/shared_libs/plays/row-identity.ts +59 -4
  23. package/package.json +5 -4
  24. package/dist/cli/index.js.map +0 -1
  25. package/dist/cli/index.mjs.map +0 -1
  26. package/dist/index.js.map +0 -1
  27. package/dist/index.mjs.map +0 -1
  28. package/dist/repo/apps/play-runner-workers/src/runtime/README.md +0 -21
  29. package/dist/repo/apps/play-runner-workers/src/runtime/batching.ts +0 -177
  30. package/dist/repo/apps/play-runner-workers/src/runtime/execution-plan.ts +0 -52
  31. package/dist/repo/apps/play-runner-workers/src/runtime/tool-batch.ts +0 -100
  32. package/dist/repo/sdk/src/cli/commands/auth.ts +0 -500
  33. package/dist/repo/sdk/src/cli/commands/billing.ts +0 -188
  34. package/dist/repo/sdk/src/cli/commands/csv.ts +0 -123
  35. package/dist/repo/sdk/src/cli/commands/db.ts +0 -119
  36. package/dist/repo/sdk/src/cli/commands/feedback.ts +0 -40
  37. package/dist/repo/sdk/src/cli/commands/org.ts +0 -117
  38. package/dist/repo/sdk/src/cli/commands/play.ts +0 -3441
  39. package/dist/repo/sdk/src/cli/commands/tools.ts +0 -687
  40. package/dist/repo/sdk/src/cli/dataset-stats.ts +0 -415
  41. package/dist/repo/sdk/src/cli/index.ts +0 -148
  42. package/dist/repo/sdk/src/cli/progress.ts +0 -149
  43. package/dist/repo/sdk/src/cli/skills-sync.ts +0 -141
  44. package/dist/repo/sdk/src/cli/trace.ts +0 -61
  45. package/dist/repo/sdk/src/cli/utils.ts +0 -145
  46. package/dist/repo/sdk/src/compat.ts +0 -77
  47. package/dist/repo/shared_libs/observability/node-tracing.ts +0 -129
  48. package/dist/repo/shared_libs/observability/tracing.ts +0 -98
  49. package/dist/repo/shared_libs/play-runtime/context.ts +0 -4242
  50. package/dist/repo/shared_libs/play-runtime/ctx-contract.ts +0 -250
  51. package/dist/repo/shared_libs/play-runtime/ctx-types.ts +0 -725
  52. package/dist/repo/shared_libs/play-runtime/dataset-id.ts +0 -10
  53. package/dist/repo/shared_libs/play-runtime/db-session-crypto.ts +0 -304
  54. package/dist/repo/shared_libs/play-runtime/db-session.ts +0 -462
  55. package/dist/repo/shared_libs/play-runtime/live-events.ts +0 -214
  56. package/dist/repo/shared_libs/play-runtime/live-state-contract.ts +0 -50
  57. package/dist/repo/shared_libs/play-runtime/map-execution-frame.ts +0 -114
  58. package/dist/repo/shared_libs/play-runtime/map-row-identity.ts +0 -158
  59. package/dist/repo/shared_libs/play-runtime/progress-emitter.ts +0 -172
  60. package/dist/repo/shared_libs/play-runtime/protocol.ts +0 -121
  61. package/dist/repo/shared_libs/play-runtime/public-play-contract.ts +0 -42
  62. package/dist/repo/shared_libs/play-runtime/result-normalization.ts +0 -33
  63. package/dist/repo/shared_libs/play-runtime/runtime-api.ts +0 -1873
  64. package/dist/repo/shared_libs/play-runtime/runtime-constraints.ts +0 -2
  65. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +0 -201
  66. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver-pg.ts +0 -48
  67. package/dist/repo/shared_libs/play-runtime/runtime-pg-driver.ts +0 -84
  68. package/dist/repo/shared_libs/play-runtime/static-pipeline-types.ts +0 -147
  69. package/dist/repo/shared_libs/play-runtime/suspension.ts +0 -68
  70. package/dist/repo/shared_libs/play-runtime/tracing.ts +0 -31
  71. package/dist/repo/shared_libs/play-runtime/waterfall-replay.ts +0 -75
  72. package/dist/repo/shared_libs/play-runtime/worker-api-types.ts +0 -140
  73. package/dist/repo/shared_libs/plays/artifact-transport.ts +0 -14
  74. package/dist/repo/shared_libs/plays/artifact-types.ts +0 -49
  75. package/dist/repo/shared_libs/plays/compiler-manifest.ts +0 -186
  76. package/dist/repo/shared_libs/plays/definition.ts +0 -264
  77. package/dist/repo/shared_libs/plays/file-refs.ts +0 -11
  78. package/dist/repo/shared_libs/plays/rate-limit-scheduler.ts +0 -206
  79. package/dist/repo/shared_libs/plays/resolve-static-pipeline.ts +0 -164
  80. package/dist/repo/shared_libs/plays/runtime-validation.ts +0 -395
  81. package/dist/repo/shared_libs/temporal/constants.ts +0 -39
  82. package/dist/repo/shared_libs/temporal/preview-config.ts +0 -153
@@ -70,6 +70,7 @@ import type {
70
70
  PlayDataset,
71
71
  PlayDatasetInput,
72
72
  } from '../../shared_libs/plays/dataset.js';
73
+ import type { ToolExecuteResult } from '../../shared_libs/play-runtime/tool-result-types.js';
73
74
  import type {
74
75
  DeeplineClientOptions,
75
76
  PlayDetail,
@@ -140,36 +141,14 @@ export type LoosePlayObject = {
140
141
  [key: string]: LoosePlayObject;
141
142
  };
142
143
 
143
- export type ToolExtractedValue<T = unknown> = {
144
- path: string;
145
- get(): T | null;
146
- };
147
-
148
- export type ToolResultEnvelope<
149
- TData = unknown,
150
- TMeta = Record<string, unknown>,
151
- > = {
152
- data: TData;
153
- meta?: TMeta;
154
- };
155
-
156
- export type ToolExecuteResult<
157
- TData = unknown,
158
- TMeta = Record<string, unknown>,
159
- > = {
160
- status: string;
161
- result: ToolResultEnvelope<TData, TMeta>;
162
- extracted: Record<string, ToolExtractedValue>;
163
- lists: Record<
164
- string,
165
- {
166
- path: string;
167
- count: number | null;
168
- keys: Record<string, string>;
169
- get(): Record<string, unknown>[];
170
- }
171
- >;
172
- };
144
+ export type {
145
+ ToolExecuteResult,
146
+ ToolExecuteResultAccessors,
147
+ ToolExecuteResultBase,
148
+ ToolResultEnvelope,
149
+ ToolResultListAccessor,
150
+ ToolResultTargetAccessor as ToolExtractedValue,
151
+ } from '../../shared_libs/play-runtime/tool-result-types.js';
173
152
 
174
153
  export type ToolExecutionRequest = {
175
154
  id: string;
@@ -242,7 +221,6 @@ export type MapStepBuilder<
242
221
  run(options?: {
243
222
  description?: string;
244
223
  staleAfterSeconds?: number;
245
- concurrency?: number;
246
224
  key?:
247
225
  | (keyof InputRow & string)
248
226
  | readonly (keyof InputRow & string)[]
@@ -270,10 +248,11 @@ export type FileInput<TMetadata = unknown> = string & {
270
248
  * stage local paths passed to that flag, and use `TRow` for row-contract
271
249
  * discovery.
272
250
  */
273
- export type CsvInput<TRow extends object = Record<string, unknown>> = FileInput<{
274
- readonly kind: 'csv';
275
- readonly row: TRow;
276
- }>;
251
+ export type CsvInput<TRow extends object = Record<string, unknown>> =
252
+ FileInput<{
253
+ readonly kind: 'csv';
254
+ readonly row: TRow;
255
+ }>;
277
256
 
278
257
  export type ColumnMap<TRow extends object> = Partial<
279
258
  Record<Extract<keyof TRow, string>, string | readonly string[]>
@@ -943,7 +922,11 @@ export class DeeplineContext {
943
922
  toolId: string,
944
923
  input: Record<string, unknown>,
945
924
  ): Promise<ToolExecuteResult> =>
946
- this.client.executeTool(toolId, input) as Promise<ToolExecuteResult>,
925
+ this.client.executeTool(
926
+ toolId,
927
+ input,
928
+ { includeToolMetadata: true },
929
+ ) as unknown as Promise<ToolExecuteResult>,
947
930
  };
948
931
  }
949
932
 
@@ -43,7 +43,7 @@ export {
43
43
  extractDefinedPlayName,
44
44
  } from '../../../shared_libs/plays/bundling/index.js';
45
45
 
46
- const PLAY_BUNDLE_CACHE_VERSION = 24;
46
+ const PLAY_BUNDLE_CACHE_VERSION = 26;
47
47
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
48
48
  const SDK_PACKAGE_ROOT = resolve(MODULE_DIR, '..', '..');
49
49
  const SOURCE_REPO_ROOT = resolve(SDK_PACKAGE_ROOT, '..');
@@ -66,13 +66,28 @@ const SDK_SOURCE_ROOT = HAS_SOURCE_BUNDLING_SOURCES
66
66
  : resolve(SDK_PACKAGE_ROOT, 'src');
67
67
  const SDK_PACKAGE_JSON = resolve(SDK_PACKAGE_ROOT, 'package.json');
68
68
  const SDK_ENTRY_FILE = resolve(SDK_SOURCE_ROOT, 'index.ts');
69
- const SDK_TYPES_ENTRY_FILE = resolve(SDK_PACKAGE_ROOT, 'dist', 'index.d.ts');
69
+ const SDK_TYPES_ENTRY_FILE = HAS_SOURCE_BUNDLING_SOURCES
70
+ ? SDK_ENTRY_FILE
71
+ : resolve(SDK_PACKAGE_ROOT, 'dist', 'index.d.ts');
70
72
  const SDK_WORKERS_ENTRY_FILE = resolve(SDK_SOURCE_ROOT, 'worker-play-entry.ts');
71
73
  const WORKERS_HARNESS_ENTRY_FILE = resolve(PROJECT_ROOT, 'apps', 'play-runner-workers', 'src', 'entry.ts');
72
74
  const WORKERS_HARNESS_FILES_DIR = resolve(PROJECT_ROOT, 'apps', 'play-runner-workers', 'src');
73
-
74
75
  let hasWarnedAboutNonDevelopmentBundling = false;
75
76
 
77
+ /**
78
+ * SDK/local bundling deliberately stays tool-metadata agnostic.
79
+ *
80
+ * The SDK's job is to turn a local play file into a portable artifact using
81
+ * only SDK/runtime sources. It must work for installed packages, source
82
+ * checkouts, CI smoke jobs, and clean worktrees without app-generated files
83
+ * such as `src/lib/generated/tool-typecheck-catalog.jsonl`.
84
+ *
85
+ * Tool-aware validation belongs to the Deepline API after upload:
86
+ * `compilePlayManifest` / server preflight runs the cloud typechecker inside
87
+ * the app deployment, where the generated catalog is a normal build artifact.
88
+ * Do not import `src/lib/plays/cloud-tool-typecheck` or any generated app
89
+ * catalog from this SDK adapter.
90
+ */
76
91
  function warnAboutNonDevelopmentBundling(filePath: string): void {
77
92
  if (hasWarnedAboutNonDevelopmentBundling) {
78
93
  return;
@@ -109,11 +124,10 @@ export function createSdkPlayBundlingAdapter(): PlayBundlingAdapter {
109
124
  sdkSourceRoot: SDK_SOURCE_ROOT,
110
125
  sdkPackageJson: SDK_PACKAGE_JSON,
111
126
  sdkEntryFile: SDK_ENTRY_FILE,
112
- sdkTypesEntryFile: HAS_SOURCE_BUNDLING_SOURCES
113
- ? SDK_ENTRY_FILE
114
- : existsSync(SDK_TYPES_ENTRY_FILE)
115
- ? SDK_TYPES_ENTRY_FILE
116
- : SDK_ENTRY_FILE,
127
+ sdkTypesEntryFile:
128
+ HAS_SOURCE_BUNDLING_SOURCES || !existsSync(SDK_TYPES_ENTRY_FILE)
129
+ ? SDK_ENTRY_FILE
130
+ : SDK_TYPES_ENTRY_FILE,
117
131
  sdkWorkersEntryFile: SDK_WORKERS_ENTRY_FILE,
118
132
  workersHarnessEntryFile: WORKERS_HARNESS_ENTRY_FILE,
119
133
  workersHarnessFilesDir: WORKERS_HARNESS_FILES_DIR,
@@ -1,7 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { readFile, stat } from 'node:fs/promises';
3
3
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
4
- import ts from 'typescript';
5
4
 
6
5
  export interface PlayLocalFileReference {
7
6
  sourceFragment: string;
@@ -48,126 +47,198 @@ function contentTypeForFile(filePath: string): string {
48
47
  return 'application/octet-stream';
49
48
  }
50
49
 
51
- function isCtxCsvCall(node: ts.CallExpression): boolean {
52
- if (!ts.isPropertyAccessExpression(node.expression)) {
53
- return false;
54
- }
55
- const target = node.expression.expression;
56
- return (
57
- ts.isIdentifier(target) &&
58
- (target.text === 'ctx' || target.text.endsWith('Ctx')) &&
59
- node.expression.name.text === 'csv'
60
- );
50
+ function stripCommentsToSpaces(source: string): string {
51
+ return source
52
+ .replace(/\/\*[\s\S]*?\*\//g, (match) => match.replace(/[^\n]/g, ' '))
53
+ .replace(/(^|[^:])\/\/.*$/gm, (match, prefix: string) =>
54
+ prefix + ' '.repeat(Math.max(0, match.length - prefix.length)),
55
+ );
61
56
  }
62
57
 
63
- function extractSourceFragment(source: string, node: ts.Node): string {
64
- return source.slice(node.getStart(), node.getEnd()).trim();
58
+ function unquoteStringLiteral(literal: string): string | null {
59
+ const trimmed = literal.trim();
60
+ const quote = trimmed[0];
61
+ if ((quote !== '"' && quote !== "'") || trimmed[trimmed.length - 1] !== quote) {
62
+ return null;
63
+ }
64
+ try {
65
+ return JSON.parse(quote === '"' ? trimmed : `"${trimmed.slice(1, -1).replace(/"/g, '\\"')}"`);
66
+ } catch {
67
+ return trimmed.slice(1, -1);
68
+ }
65
69
  }
66
70
 
67
- function referencesInputIdentifier(node: ts.Node): boolean {
68
- if (ts.isIdentifier(node) && node.text === 'input') {
69
- return true;
71
+ function splitTopLevelPlus(expression: string): string[] | null {
72
+ const parts: string[] = [];
73
+ let start = 0;
74
+ let depth = 0;
75
+ let quote: string | null = null;
76
+ let escaped = false;
77
+
78
+ for (let index = 0; index < expression.length; index += 1) {
79
+ const char = expression[index]!;
80
+ if (quote) {
81
+ if (escaped) {
82
+ escaped = false;
83
+ } else if (char === '\\') {
84
+ escaped = true;
85
+ } else if (char === quote) {
86
+ quote = null;
87
+ }
88
+ continue;
89
+ }
90
+ if (char === '"' || char === "'" || char === '`') {
91
+ quote = char;
92
+ continue;
93
+ }
94
+ if (char === '(' || char === '[' || char === '{') depth += 1;
95
+ if (char === ')' || char === ']' || char === '}') depth -= 1;
96
+ if (char === '+' && depth === 0) {
97
+ parts.push(expression.slice(start, index));
98
+ start = index + 1;
99
+ }
70
100
  }
71
101
 
72
- return node.getChildren().some((child) => referencesInputIdentifier(child));
102
+ if (parts.length === 0) return null;
103
+ parts.push(expression.slice(start));
104
+ return parts;
73
105
  }
74
106
 
75
- function isRuntimeInputExpression(node: ts.Expression): boolean {
76
- if (ts.isPropertyAccessExpression(node)) {
77
- return ts.isIdentifier(node.expression) && node.expression.text === 'input';
78
- }
79
- if (ts.isElementAccessExpression(node)) {
80
- return ts.isIdentifier(node.expression) && node.expression.text === 'input';
81
- }
82
- if (ts.isIdentifier(node)) {
83
- return node.text === 'input';
107
+ function stripOuterParens(expression: string): string {
108
+ let value = expression.trim();
109
+ while (value.startsWith('(') && value.endsWith(')')) {
110
+ value = value.slice(1, -1).trim();
84
111
  }
112
+ return value;
113
+ }
85
114
 
86
- if (ts.isParenthesizedExpression(node)) {
87
- return isRuntimeInputExpression(node.expression);
88
- }
115
+ function isRuntimeInputExpression(expression: string): boolean {
116
+ return /(^|[^\w$])input([^\w$]|$)/.test(expression);
117
+ }
89
118
 
90
- if (
91
- ts.isBinaryExpression(node) &&
92
- (node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ||
93
- node.operatorToken.kind === ts.SyntaxKind.BarBarToken)
94
- ) {
95
- return isRuntimeInputExpression(node.left) || isRuntimeInputExpression(node.right);
119
+ function resolveStringExpression(expression: string, constants: ConstMap): string | null {
120
+ const value = stripOuterParens(expression);
121
+ if (/^(['"])(?:\\.|(?!\1)[\s\S])*\1$/.test(value)) {
122
+ return unquoteStringLiteral(value);
96
123
  }
97
-
98
- if (ts.isConditionalExpression(node)) {
99
- return (
100
- isRuntimeInputExpression(node.condition) ||
101
- isRuntimeInputExpression(node.whenTrue) ||
102
- isRuntimeInputExpression(node.whenFalse)
103
- );
124
+ if (/^`(?:\\.|[^`$]|\$(?!\{))*`$/.test(value)) {
125
+ return value.slice(1, -1);
104
126
  }
105
-
106
- return referencesInputIdentifier(node);
107
- }
108
-
109
- function resolveStringExpression(node: ts.Expression, constants: ConstMap): string | null {
110
- if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
111
- return node.text;
127
+ if (/^[A-Za-z_$][\w$]*$/.test(value)) {
128
+ return constants.get(value) ?? null;
112
129
  }
113
-
114
- if (ts.isParenthesizedExpression(node)) {
115
- return resolveStringExpression(node.expression, constants);
130
+ const parts = splitTopLevelPlus(value);
131
+ if (parts) {
132
+ const resolved = parts.map((part) => resolveStringExpression(part, constants));
133
+ return resolved.every((part): part is string => part != null)
134
+ ? resolved.join('')
135
+ : null;
116
136
  }
137
+ return null;
138
+ }
117
139
 
118
- if (ts.isIdentifier(node)) {
119
- return constants.get(node.text) ?? null;
140
+ function collectTopLevelStringConstants(sourceCode: string): ConstMap {
141
+ const constants: ConstMap = new Map();
142
+ const source = stripCommentsToSpaces(sourceCode);
143
+ for (const match of source.matchAll(/(?:^|\n)\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+)/g)) {
144
+ const resolved = resolveStringExpression(match[2]!, constants);
145
+ if (resolved != null) {
146
+ constants.set(match[1]!, resolved);
147
+ }
120
148
  }
149
+ return constants;
150
+ }
121
151
 
122
- if (ts.isTemplateExpression(node)) {
123
- let value = node.head.text;
124
- for (const span of node.templateSpans) {
125
- const resolved = resolveStringExpression(span.expression, constants);
126
- if (resolved == null) {
127
- return null;
152
+ function findMatchingGenericEnd(source: string, openIndex: number): number {
153
+ let depth = 0;
154
+ let quote: string | null = null;
155
+ let escaped = false;
156
+ for (let index = openIndex; index < source.length; index += 1) {
157
+ const char = source[index]!;
158
+ if (quote) {
159
+ if (escaped) {
160
+ escaped = false;
161
+ } else if (char === '\\') {
162
+ escaped = true;
163
+ } else if (char === quote) {
164
+ quote = null;
128
165
  }
129
- value += resolved + span.literal.text;
166
+ continue;
130
167
  }
131
- return value;
132
- }
133
-
134
- if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
135
- const left = resolveStringExpression(node.left, constants);
136
- const right = resolveStringExpression(node.right, constants);
137
- if (left == null || right == null) {
138
- return null;
168
+ if (char === '"' || char === "'" || char === '`') {
169
+ quote = char;
170
+ continue;
171
+ }
172
+ if (char === '<') depth += 1;
173
+ if (char === '>') {
174
+ depth -= 1;
175
+ if (depth === 0) return index;
139
176
  }
140
- return left + right;
141
177
  }
142
-
143
- return null;
178
+ return -1;
144
179
  }
145
180
 
146
- function collectTopLevelStringConstants(sourceFile: ts.SourceFile): ConstMap {
147
- const constants: ConstMap = new Map();
181
+ function findCallOpenParen(source: string, afterCsvIndex: number): number {
182
+ let index = afterCsvIndex;
183
+ while (/\s/.test(source[index] ?? '')) index += 1;
184
+ if (source[index] === '<') {
185
+ const genericEnd = findMatchingGenericEnd(source, index);
186
+ if (genericEnd < 0) return -1;
187
+ index = genericEnd + 1;
188
+ while (/\s/.test(source[index] ?? '')) index += 1;
189
+ }
190
+ return source[index] === '(' ? index : -1;
191
+ }
148
192
 
149
- for (const statement of sourceFile.statements) {
150
- if (!ts.isVariableStatement(statement)) {
193
+ function firstCallArgument(source: string, openParen: number): { text: string; start: number; end: number } | null {
194
+ let depth = 0;
195
+ let quote: string | null = null;
196
+ let escaped = false;
197
+ const start = openParen + 1;
198
+
199
+ for (let index = start; index < source.length; index += 1) {
200
+ const char = source[index]!;
201
+ if (quote) {
202
+ if (escaped) {
203
+ escaped = false;
204
+ } else if (char === '\\') {
205
+ escaped = true;
206
+ } else if (char === quote) {
207
+ quote = null;
208
+ }
151
209
  continue;
152
210
  }
153
-
154
- if (!(statement.declarationList.flags & ts.NodeFlags.Const)) {
211
+ if (char === '"' || char === "'" || char === '`') {
212
+ quote = char;
155
213
  continue;
156
214
  }
157
-
158
- for (const declaration of statement.declarationList.declarations) {
159
- if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
160
- continue;
161
- }
162
-
163
- const resolved = resolveStringExpression(declaration.initializer, constants);
164
- if (resolved != null) {
165
- constants.set(declaration.name.text, resolved);
166
- }
215
+ if (char === '(' || char === '[' || char === '{') depth += 1;
216
+ if (char === ')' && depth === 0) {
217
+ const text = source.slice(start, index).trim();
218
+ return text ? { text, start, end: index } : null;
167
219
  }
220
+ if (char === ',' && depth === 0) {
221
+ const text = source.slice(start, index).trim();
222
+ return text ? { text, start, end: index } : null;
223
+ }
224
+ if (char === ')' || char === ']' || char === '}') depth -= 1;
168
225
  }
169
226
 
170
- return constants;
227
+ return null;
228
+ }
229
+
230
+ function localImportSpecifiers(sourceCode: string): string[] {
231
+ const source = stripCommentsToSpaces(sourceCode);
232
+ const specifiers: string[] = [];
233
+ for (const match of source.matchAll(
234
+ /\b(?:import|export)\s+(?!type\b)(?:[\s\S]*?\s+from\s*)?['"]([^'"]+)['"]/g,
235
+ )) {
236
+ if (match[1]?.startsWith('.')) specifiers.push(match[1]);
237
+ }
238
+ for (const match of source.matchAll(/\brequire\s*\(\s*(['"])(\.[^'"]*)\1\s*\)/g)) {
239
+ specifiers.push(match[2]!);
240
+ }
241
+ return specifiers;
171
242
  }
172
243
 
173
244
  async function fileExists(filePath: string): Promise<boolean> {
@@ -223,88 +294,64 @@ export async function discoverPackagedLocalFiles(
223
294
  visitedFiles.add(absolutePath);
224
295
 
225
296
  const sourceCode = await readFile(absolutePath, 'utf-8');
226
- const sourceFile = ts.createSourceFile(
227
- absolutePath,
228
- sourceCode,
229
- ts.ScriptTarget.Latest,
230
- true,
231
- ts.ScriptKind.TS,
232
- );
233
- const constants = collectTopLevelStringConstants(sourceFile);
297
+ const scanSource = stripCommentsToSpaces(sourceCode);
298
+ const constants = collectTopLevelStringConstants(sourceCode);
234
299
  const childVisits: Promise<void>[] = [];
235
300
 
236
- const visitNode = async (node: ts.Node): Promise<void> => {
237
- if (ts.isCallExpression(node) && isCtxCsvCall(node)) {
238
- const argument = node.arguments[0];
239
- if (!argument) {
301
+ for (const match of scanSource.matchAll(/\b([A-Za-z_$][\w$]*)\s*\.\s*csv\b/g)) {
302
+ const target = match[1]!;
303
+ if (target !== 'ctx' && !target.endsWith('Ctx')) {
304
+ continue;
305
+ }
306
+ const openParen = findCallOpenParen(scanSource, match.index! + match[0].length);
307
+ if (openParen < 0) {
308
+ continue;
309
+ }
310
+ const argument = firstCallArgument(scanSource, openParen);
311
+ if (!argument) {
312
+ unresolved.push({
313
+ sourceFragment: 'ctx.csv()',
314
+ message: 'ctx.csv() requires a file path string or input reference.',
315
+ });
316
+ } else if (!isRuntimeInputExpression(argument.text)) {
317
+ const resolvedPath = resolveStringExpression(argument.text, constants);
318
+ if (resolvedPath == null) {
240
319
  unresolved.push({
241
- sourceFragment: 'ctx.csv()',
242
- message: 'ctx.csv() requires a file path string or input reference.',
320
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
321
+ message:
322
+ 'Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file.',
243
323
  });
244
- } else if (!isRuntimeInputExpression(argument)) {
245
- const resolvedPath = resolveStringExpression(argument, constants);
246
- if (resolvedPath == null) {
324
+ } else {
325
+ const absoluteCsvPath = resolve(dirname(absolutePath), resolvedPath);
326
+ if (isAbsolute(resolvedPath) || !isPathInsideDirectory(absoluteCsvPath, packagingRoot)) {
247
327
  unresolved.push({
248
- sourceFragment: extractSourceFragment(sourceCode, argument),
328
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
249
329
  message:
250
- 'Could not resolve this ctx.csv(...) path at submit time. Use a string literal, a top-level const string, or pass a runtime input like input.file.',
251
- });
252
- } else {
253
- const absoluteCsvPath = resolve(dirname(absolutePath), resolvedPath);
254
- if (isAbsolute(resolvedPath) || !isPathInsideDirectory(absoluteCsvPath, packagingRoot)) {
255
- unresolved.push({
256
- sourceFragment: extractSourceFragment(sourceCode, argument),
257
- message:
258
- 'ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead.',
259
- });
260
- return;
261
- }
262
- const buffer = await readFile(absoluteCsvPath);
263
- const stats = await stat(absoluteCsvPath);
264
- files.set(absoluteCsvPath, {
265
- sourceFragment: extractSourceFragment(sourceCode, argument),
266
- logicalPath: resolvedPath,
267
- absolutePath: absoluteCsvPath,
268
- bytes: stats.size,
269
- contentHash: sha256(buffer),
270
- contentType: contentTypeForFile(absoluteCsvPath),
330
+ 'ctx.csv(...) packaged file paths must be relative paths inside the play directory. Pass external files at runtime with input.file instead.',
271
331
  });
332
+ continue;
272
333
  }
334
+ const buffer = await readFile(absoluteCsvPath);
335
+ const stats = await stat(absoluteCsvPath);
336
+ files.set(absoluteCsvPath, {
337
+ sourceFragment: sourceCode.slice(argument.start, argument.end).trim(),
338
+ logicalPath: resolvedPath,
339
+ absolutePath: absoluteCsvPath,
340
+ bytes: stats.size,
341
+ contentHash: sha256(buffer),
342
+ contentType: contentTypeForFile(absoluteCsvPath),
343
+ });
273
344
  }
274
345
  }
346
+ }
275
347
 
276
- if (
277
- ts.isImportDeclaration(node) &&
278
- !node.importClause?.isTypeOnly &&
279
- ts.isStringLiteral(node.moduleSpecifier) &&
280
- node.moduleSpecifier.text.startsWith('.')
281
- ) {
282
- childVisits.push(
283
- resolveLocalImport(absolutePath, node.moduleSpecifier.text).then((resolvedImport) =>
284
- visitSourceFile(resolvedImport),
285
- ),
286
- );
287
- }
288
-
289
- if (
290
- ts.isCallExpression(node) &&
291
- ts.isIdentifier(node.expression) &&
292
- node.expression.text === 'require' &&
293
- node.arguments.length === 1 &&
294
- ts.isStringLiteral(node.arguments[0]) &&
295
- node.arguments[0].text.startsWith('.')
296
- ) {
297
- childVisits.push(
298
- resolveLocalImport(absolutePath, node.arguments[0].text).then((resolvedImport) =>
299
- visitSourceFile(resolvedImport),
300
- ),
301
- );
302
- }
303
-
304
- await Promise.all(node.getChildren(sourceFile).map((child) => visitNode(child)));
305
- };
306
-
307
- await visitNode(sourceFile);
348
+ for (const specifier of localImportSpecifiers(sourceCode)) {
349
+ childVisits.push(
350
+ resolveLocalImport(absolutePath, specifier).then((resolvedImport) =>
351
+ visitSourceFile(resolvedImport),
352
+ ),
353
+ );
354
+ }
308
355
  await Promise.all(childVisits);
309
356
  };
310
357
 
@@ -114,6 +114,27 @@ export interface ToolDefinition {
114
114
  resultIdentityGetters?: Record<string, string[]>;
115
115
  /** Whether the output is a direct object or wrapped in a `result` envelope. */
116
116
  rowContextShape?: 'direct' | 'result_envelope';
117
+ /** Search relevance score returned by ranked tool search. */
118
+ search_score?: number;
119
+ /** Search match snippets returned by ranked tool search. */
120
+ search_matches?: Array<{
121
+ field: string;
122
+ value: string;
123
+ term?: string;
124
+ }>;
125
+ }
126
+
127
+ export interface ToolSearchOptions {
128
+ query?: string;
129
+ categories?: string;
130
+ searchTerms?: string;
131
+ searchMode?: 'v1' | 'v2';
132
+ includeSearchDebug?: boolean;
133
+ }
134
+
135
+ export interface ToolSearchResult {
136
+ tools: ToolDefinition[];
137
+ search_fallback_to_category?: boolean;
117
138
  }
118
139
 
119
140
  /**
@@ -201,6 +222,8 @@ export interface PlayProgressStatus {
201
222
  totalRows?: number;
202
223
  /** Accumulated log lines from `ctx.log()`. Grows monotonically. */
203
224
  logs: string[];
225
+ /** Zero-based offset of the first returned log line when a tail cursor is used. */
226
+ logOffset?: number;
204
227
  /** Error message if the play has failed. */
205
228
  error?: string;
206
229
  }
@@ -606,6 +629,8 @@ export interface StartPlayRunRequest {
606
629
  artifactStorageKey?: string;
607
630
  /** Source snapshot already validated while registering this artifact. */
608
631
  sourceCode?: string;
632
+ /** Source graph snapshots for local helper files included in cloud preflight. */
633
+ sourceFiles?: Record<string, string>;
609
634
  /** Static pipeline already produced while registering this artifact. */
610
635
  staticPipeline?: unknown;
611
636
  /** Artifact content hash already validated while registering this artifact. */
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.1.12";
2
- export const SDK_API_CONTRACT = "2026-04-plays-v1";
1
+ export const SDK_VERSION = "0.1.20";
2
+ export const SDK_API_CONTRACT = "2026-05-runs-v2";