@datalayer/agent-runtimes 1.3.29 → 1.3.32
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/lib/AgentNode.js +14 -33
- package/lib/chat/ChatFloating.js +40 -8
- package/lib/chat/assistant/AssistantStage.d.ts +26 -7
- package/lib/chat/assistant/AssistantStage.js +115 -8
- package/lib/chat/assistant/SpeechBalloon.js +7 -3
- package/lib/chat/assistant/characters.d.ts +32 -1
- package/lib/chat/assistant/characters.js +180 -15
- package/lib/chat/assistant/state.d.ts +28 -0
- package/lib/chat/assistant/state.js +31 -0
- package/lib/chat/base/ChatBase.js +74 -50
- package/lib/chat/base/modelChoice.d.ts +40 -1
- package/lib/chat/base/modelChoice.js +42 -15
- package/lib/chat/prompt/InputPrompt.d.ts +7 -1
- package/lib/chat/prompt/InputPrompt.js +2 -2
- package/lib/chat/prompt/InputPromptBase.js +1 -1
- package/lib/chat/prompt/menus/ModelSelector.d.ts +13 -1
- package/lib/chat/prompt/menus/ModelSelector.js +17 -5
- package/lib/chat/useViewportDrag.d.ts +9 -1
- package/lib/chat/useViewportDrag.js +4 -4
- package/lib/components/a2ui/index.d.ts +2 -0
- package/lib/components/a2ui/index.js +2 -0
- package/lib/components/a2ui/visibility.d.ts +15 -0
- package/lib/components/a2ui/visibility.js +24 -0
- package/lib/components/a2ui/visibleWhen.d.ts +17 -0
- package/lib/components/a2ui/visibleWhen.js +54 -0
- package/lib/examples/ChatAssistantExample.d.ts +4 -0
- package/lib/examples/ChatAssistantExample.js +26 -5
- package/lib/examples/utils/owlCharacterPlugin.d.ts +7 -0
- package/lib/examples/utils/owlCharacterPlugin.js +44 -0
- package/lib/loop/apps/AppFeedback.d.ts +27 -0
- package/lib/loop/apps/AppFeedback.js +78 -0
- package/lib/loop/apps/AppRenderer.d.ts +33 -1
- package/lib/loop/apps/AppRenderer.js +111 -29
- package/lib/loop/apps/agent.d.ts +15 -0
- package/lib/loop/apps/agent.js +42 -0
- package/lib/loop/apps/appspec.js +6 -3
- package/lib/loop/apps/checks.d.ts +10 -2
- package/lib/loop/apps/checks.js +21 -10
- package/lib/loop/apps/feedback.d.ts +51 -0
- package/lib/loop/apps/feedback.js +61 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/rules.d.ts +14 -0
- package/lib/loop/apps/rules.js +24 -0
- package/lib/loop/core/canvasBlocks.d.ts +16 -0
- package/lib/loop/core/canvasBlocks.js +13 -0
- package/lib/loop/core/index.d.ts +15 -0
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +42 -0
- package/lib/loop/embed/AppEmbed.js +205 -0
- package/lib/loop/embed/datalayer-app.d.ts +1 -0
- package/lib/loop/embed/datalayer-app.js +16 -0
- package/lib/loop/embed/element.d.ts +57 -0
- package/lib/loop/embed/element.js +405 -0
- package/lib/loop/embed/embedConfig.d.ts +120 -0
- package/lib/loop/embed/embedConfig.js +148 -0
- package/lib/loop/embed/embedTheme.d.ts +24 -0
- package/lib/loop/embed/embedTheme.js +83 -0
- package/lib/loop/index.d.ts +6 -2
- package/lib/loop/index.js +8 -2
- package/lib/loop/plugins/a2ui/A2uiView.js +2 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +23 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +36 -9
- package/lib/loop/plugins/agent-capacity/index.d.ts +4 -0
- package/lib/loop/plugins/agent-capacity/index.js +3 -1
- package/lib/loop/plugins/agents/switchable.d.ts +7 -0
- package/lib/loop/plugins/agents/switchable.js +91 -88
- package/lib/loop/plugins/app-page/AppPage.d.ts +16 -0
- package/lib/loop/plugins/app-page/AppPage.js +53 -0
- package/lib/loop/plugins/app-page/appPageModel.d.ts +180 -0
- package/lib/loop/plugins/app-page/appPageModel.js +443 -0
- package/lib/loop/plugins/app-page/index.d.ts +8 -0
- package/lib/loop/plugins/app-page/index.js +34 -0
- package/lib/loop/plugins/canvas-blocks/index.d.ts +63 -0
- package/lib/loop/plugins/canvas-blocks/index.js +118 -0
- package/lib/loop/plugins/chat/ChatView.js +52 -26
- package/lib/loop/plugins/chat/turnState.d.ts +5 -2
- package/lib/loop/plugins/chat/turnState.js +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +31 -0
- package/lib/loop/plugins/page-layout/SplitLayout.js +129 -0
- package/lib/loop/plugins/page-layout/index.d.ts +13 -0
- package/lib/loop/plugins/page-layout/index.js +10 -1
- package/lib/loop/presets.d.ts +12 -0
- package/lib/loop/presets.js +5 -3
- package/lib/specs/actions.js +9 -0
- package/lib/specs/agents/agents.js +20 -4
- package/lib/specs/apps.d.ts +10 -1
- package/lib/specs/apps.js +1779 -154
- package/lib/specs/appspecSchema.js +14 -8
- package/lib/specs/models.d.ts +1 -1
- package/lib/specs/models.js +8 -8
- package/lib/specs/tools.d.ts +1 -0
- package/lib/specs/tools.js +19 -0
- package/lib/stories/loop/LoopReference.stories.d.ts +21 -0
- package/lib/stories/loop/LoopReference.stories.js +31 -0
- package/lib/stories/loop/ReferenceScreens.d.ts +53 -0
- package/lib/stories/loop/ReferenceScreens.js +256 -0
- package/lib/stories/loop/pictures-main.d.ts +1 -0
- package/lib/stories/loop/pictures-main.js +64 -0
- package/lib/types/agentspecs.d.ts +17 -1
- package/lib/types/chat.d.ts +5 -0
- package/lib/types/config.d.ts +20 -0
- package/package.json +10 -6
- package/scripts/check-cloudflare-models.py +2 -2
- package/scripts/codegen/generate_agents.py +50 -2
- package/scripts/codegen/generate_apps.py +38 -7
- package/scripts/codegen/generate_models.py +4 -4
|
@@ -372,6 +372,12 @@ export interface ServerActionsSpec {
|
|
|
372
372
|
}
|
|
373
373
|
/** What kind of application it is: what its user meets. */
|
|
374
374
|
export type AppKind = 'chat' | 'widget' | 'decision' | 'worker';
|
|
375
|
+
/**
|
|
376
|
+
* How an application was built: in Python (its `app.py`), on the Canvas (its
|
|
377
|
+
* page composed there) or written out as its spec. Carried beside the
|
|
378
|
+
* catalogue (`APP_BUILT`), since an Appspec does not hold it.
|
|
379
|
+
*/
|
|
380
|
+
export type AppBuilt = 'python' | 'canvas' | 'written';
|
|
375
381
|
/** What an application does when it meets an action. */
|
|
376
382
|
export type AppBehaviour = 'do_it' | 'if_asked' | 'ask_first' | 'leave_to_me';
|
|
377
383
|
export type AppLayout = 'chat' | 'page' | 'split';
|
|
@@ -470,6 +476,11 @@ export interface AppRecordSpec {
|
|
|
470
476
|
keepFor: string;
|
|
471
477
|
retentionDays: number;
|
|
472
478
|
include: string[];
|
|
479
|
+
/**
|
|
480
|
+
* Whether its conversations may be used to suggest tests (LOOP V-16): off
|
|
481
|
+
* unless said, and only those kept while it is on are sampled.
|
|
482
|
+
*/
|
|
483
|
+
suggestTests: boolean;
|
|
473
484
|
}
|
|
474
485
|
/** Optional checks from the catalogue. */
|
|
475
486
|
export interface AppChecksSpec {
|
|
@@ -516,7 +527,7 @@ export interface AppDecisionSpec {
|
|
|
516
527
|
criteria: AppCriterionSpec[];
|
|
517
528
|
minConfidence: number;
|
|
518
529
|
scenarios: AppScenarioSpec[];
|
|
519
|
-
|
|
530
|
+
decisionModel: string;
|
|
520
531
|
}
|
|
521
532
|
/**
|
|
522
533
|
* An application (`agentspecs/apps`): the Appspec. An agent with an interface,
|
|
@@ -595,6 +606,11 @@ export interface Agentspec {
|
|
|
595
606
|
enabled: boolean;
|
|
596
607
|
/** AI model identifier to use for this agent */
|
|
597
608
|
model?: string;
|
|
609
|
+
/**
|
|
610
|
+
* Other models of the catalogue this agent may be switched to, beside its
|
|
611
|
+
* `model`. The runtime offers those of them its inference serves.
|
|
612
|
+
*/
|
|
613
|
+
modelAdditionals?: string[];
|
|
598
614
|
/** Inference provider routing strategy */
|
|
599
615
|
inferenceProvider?: 'local' | 'datalayer';
|
|
600
616
|
/** MCP servers used by this agent */
|
package/lib/types/chat.d.ts
CHANGED
|
@@ -653,6 +653,11 @@ export interface ChatBaseProps {
|
|
|
653
653
|
stop: () => void;
|
|
654
654
|
/** Start the conversation over — what the header's + does. */
|
|
655
655
|
newChat: () => void;
|
|
656
|
+
/**
|
|
657
|
+
* The conversation the next message goes to, as the runtime knows it
|
|
658
|
+
* (AG-UI's thread): what an application's record keeps it under.
|
|
659
|
+
*/
|
|
660
|
+
thread: () => string;
|
|
656
661
|
} | null) => void;
|
|
657
662
|
/**
|
|
658
663
|
* Reports whether the chat is streaming a reply, so a host that owns the
|
package/lib/types/config.d.ts
CHANGED
|
@@ -45,7 +45,27 @@ export interface AgentConfig {
|
|
|
45
45
|
* Remote configuration from server
|
|
46
46
|
*/
|
|
47
47
|
export interface RemoteConfig {
|
|
48
|
+
/**
|
|
49
|
+
* The models on offer — for an agent, its `model` and `model_additionals` —
|
|
50
|
+
* each saying whether it can be used.
|
|
51
|
+
*/
|
|
48
52
|
models: ModelConfig[];
|
|
53
|
+
/**
|
|
54
|
+
* Who decided which models can be used: ai-inference's own list, or the
|
|
55
|
+
* runtime's configuration when it did not route through ai-inference or
|
|
56
|
+
* ai-inference did not answer.
|
|
57
|
+
*/
|
|
58
|
+
modelsSource?: 'ai-inference' | 'local';
|
|
59
|
+
/** That decision in a sentence. */
|
|
60
|
+
modelsNote?: string;
|
|
61
|
+
/**
|
|
62
|
+
* The typed-decision models ai-inference serves (Jev), apart from `models`:
|
|
63
|
+
* a decision asks them, no agent runs on them, so they are never offered
|
|
64
|
+
* as the agent's model.
|
|
65
|
+
*/
|
|
66
|
+
decisionModels: ModelConfig[];
|
|
67
|
+
/** What a typed-decision model is for, in a sentence. */
|
|
68
|
+
decisionsNote: string;
|
|
49
69
|
defaultModel?: string;
|
|
50
70
|
builtinTools: BuiltinTool[];
|
|
51
71
|
mcpServers?: MCPServerConfig[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalayer/agent-runtimes",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.32",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
".",
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"build": "npm run clean && gulp resources-to-lib && tsc -b && cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build",
|
|
57
57
|
"build-storybook": "storybook build",
|
|
58
58
|
"build:all": "npm run build && npm run build:examples",
|
|
59
|
+
"build:embed": "cross-env NODE_OPTIONS=--max-old-space-size=8192 vite build --config vite.embed.config.ts",
|
|
59
60
|
"build:examples": "npm run build:nextjs",
|
|
60
61
|
"build:lib": "npm run clean:lib && gulp resources-to-lib && tsc -b",
|
|
61
62
|
"build:nextjs": "npm run build --workspace=nextjs-notebook-example",
|
|
@@ -117,6 +118,8 @@
|
|
|
117
118
|
"test:all": "vitest run",
|
|
118
119
|
"test:coverage": "vitest run --coverage --project=unit --project=general",
|
|
119
120
|
"test:integration": "vitest run --project=integration",
|
|
121
|
+
"test:pictures": "playwright test -c pictures/playwright.config.ts",
|
|
122
|
+
"test:pictures:accept": "playwright test -c pictures/playwright.config.ts --update-snapshots=changed",
|
|
120
123
|
"test:ui": "vitest --ui --project=unit --project=general",
|
|
121
124
|
"test:unit": "vitest run --project=unit",
|
|
122
125
|
"test:watch": "vitest --project=unit --project=general",
|
|
@@ -130,9 +133,9 @@
|
|
|
130
133
|
},
|
|
131
134
|
"dependencies": {
|
|
132
135
|
"@a2a-js/sdk": "^0.3.6",
|
|
133
|
-
"@a2ui/markdown-it": "0.
|
|
134
|
-
"@a2ui/react": "0.
|
|
135
|
-
"@a2ui/web_core": "0.
|
|
136
|
+
"@a2ui/markdown-it": "0.2.0",
|
|
137
|
+
"@a2ui/react": "0.12.0",
|
|
138
|
+
"@a2ui/web_core": "0.12.0",
|
|
136
139
|
"@ag-ui/client": "^0.0.42",
|
|
137
140
|
"@ag-ui/core": "^0.0.57",
|
|
138
141
|
"@ag-ui/encoder": "^0.0.57",
|
|
@@ -275,6 +278,7 @@
|
|
|
275
278
|
"@copilotkit/runtime": "1.10.6",
|
|
276
279
|
"@datalayer/reactor-graph": "^1.0.4",
|
|
277
280
|
"@eslint/js": "^10.0.1",
|
|
281
|
+
"@playwright/test": "^1.63.0",
|
|
278
282
|
"@storybook/addon-a11y": "^9.1.1",
|
|
279
283
|
"@storybook/addon-docs": "^9.1.1",
|
|
280
284
|
"@storybook/addon-onboarding": "^9.1.1",
|
|
@@ -328,7 +332,7 @@
|
|
|
328
332
|
"vitest": "^3.2.4"
|
|
329
333
|
},
|
|
330
334
|
"resolutions": {
|
|
331
|
-
"@a2ui/web_core": "0.
|
|
335
|
+
"@a2ui/web_core": "0.12.0",
|
|
332
336
|
"@langchain/core": "^0.3.80",
|
|
333
337
|
"@ai-sdk/react": "4.0.93",
|
|
334
338
|
"@microsoft/fast-colors": "5.3.1",
|
|
@@ -347,7 +351,7 @@
|
|
|
347
351
|
"typescript": "5.9.3"
|
|
348
352
|
},
|
|
349
353
|
"overrides": {
|
|
350
|
-
"@a2ui/web_core": "0.
|
|
354
|
+
"@a2ui/web_core": "0.12.0",
|
|
351
355
|
"@langchain/core": "^0.3.80",
|
|
352
356
|
"@ai-sdk/react": "4.0.93",
|
|
353
357
|
"@microsoft/fast-colors": "5.3.1",
|
|
@@ -69,14 +69,14 @@ def served(account: str, token: str) -> dict[str, dict]:
|
|
|
69
69
|
def specs() -> list[dict]:
|
|
70
70
|
"""The Workers AI specs (``cloudflare-wrk-*.yaml``) that name a model Cloudflare
|
|
71
71
|
hosts under ``@cf/``. The gateway flavour (``cloudflare-gtw-*``) and the typed
|
|
72
|
-
|
|
72
|
+
decision models (``typesafe/jev``, with no ``@cf/`` namespace) are not in the
|
|
73
73
|
account's model listing and are left out.
|
|
74
74
|
"""
|
|
75
75
|
found = []
|
|
76
76
|
for path in sorted(SPECS.glob("cloudflare-wrk-*.yaml")):
|
|
77
77
|
with open(path) as handle:
|
|
78
78
|
data = yaml.safe_load(handle) or {}
|
|
79
|
-
if "
|
|
79
|
+
if "decisions" in (data.get("capabilities") or []):
|
|
80
80
|
continue
|
|
81
81
|
found.append({"file": path.name, **data})
|
|
82
82
|
return found
|
|
@@ -276,9 +276,44 @@ def load_yaml_specs(specs_dir: Path) -> List[tuple[str, Dict[str, Any]]]:
|
|
|
276
276
|
spec = resolve_spec(spec, by_id, fragments_by_id)
|
|
277
277
|
resolved.append((subfolder, spec))
|
|
278
278
|
|
|
279
|
+
check_model_additionals(resolved, load_model_ids(specs_dir))
|
|
279
280
|
return resolved
|
|
280
281
|
|
|
281
282
|
|
|
283
|
+
def load_model_ids(specs_dir: Path) -> set[str]:
|
|
284
|
+
"""Every id the models catalogue answers to: its ids and their aliases."""
|
|
285
|
+
ids: set[str] = set()
|
|
286
|
+
for yaml_file in sorted((specs_dir.parent / "models").glob("*.yaml")):
|
|
287
|
+
with open(yaml_file, "r") as f:
|
|
288
|
+
model = yaml.safe_load(f) or {}
|
|
289
|
+
if model.get("id"):
|
|
290
|
+
ids.add(str(model["id"]))
|
|
291
|
+
ids.update(str(alias) for alias in model.get("aliases") or [])
|
|
292
|
+
return ids
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def check_model_additionals(
|
|
296
|
+
specs: List[tuple[str, Dict[str, Any]]], model_ids: set[str]
|
|
297
|
+
) -> None:
|
|
298
|
+
"""Refuse an agent whose `model_additionals` names a model the catalogue does not know."""
|
|
299
|
+
problems = []
|
|
300
|
+
for _, spec in specs:
|
|
301
|
+
additionals = spec.get("model_additionals")
|
|
302
|
+
if additionals is None:
|
|
303
|
+
continue
|
|
304
|
+
if not isinstance(additionals, list):
|
|
305
|
+
problems.append(f"{spec.get('id')}: model_additionals is not a list")
|
|
306
|
+
continue
|
|
307
|
+
unknown = [str(m) for m in additionals if str(m) not in model_ids]
|
|
308
|
+
if unknown:
|
|
309
|
+
problems.append(
|
|
310
|
+
f"{spec.get('id')}: model_additionals names models the catalogue "
|
|
311
|
+
f"does not know: {', '.join(unknown)}"
|
|
312
|
+
)
|
|
313
|
+
if problems:
|
|
314
|
+
raise SystemExit("\n".join(problems))
|
|
315
|
+
|
|
316
|
+
|
|
282
317
|
def generate_python_code(specs: List[tuple[str, Dict[str, Any]]]) -> str:
|
|
283
318
|
"""Generate Python code from agent specifications."""
|
|
284
319
|
# Header
|
|
@@ -423,6 +458,12 @@ from agent_runtimes.types import (
|
|
|
423
458
|
# Model field
|
|
424
459
|
model_id = spec.get("model")
|
|
425
460
|
model_str = f'"{model_id}"' if model_id else "None"
|
|
461
|
+
model_additionals = spec.get("model_additionals") or []
|
|
462
|
+
model_additionals_py_line = (
|
|
463
|
+
f" model_additionals={_fmt_list(model_additionals)},\n"
|
|
464
|
+
if model_additionals
|
|
465
|
+
else ""
|
|
466
|
+
)
|
|
426
467
|
inference_provider = spec.get("inference_provider")
|
|
427
468
|
inference_provider_str = (
|
|
428
469
|
f'"{inference_provider}"' if inference_provider else "None"
|
|
@@ -547,7 +588,7 @@ from agent_runtimes.types import (
|
|
|
547
588
|
domain={f'"{domain_value}"' if domain_value else "None"},
|
|
548
589
|
enabled={spec.get("enabled", True)},
|
|
549
590
|
model={model_str},
|
|
550
|
-
inference_provider={inference_provider_str},
|
|
591
|
+
{model_additionals_py_line} inference_provider={inference_provider_str},
|
|
551
592
|
mcp_servers=[{mcp_servers_str}],
|
|
552
593
|
skills={_fmt_list(skill_refs)},
|
|
553
594
|
tools={_fmt_list(tool_refs)},
|
|
@@ -1045,6 +1086,12 @@ const FRONTEND_TOOL_MAP: Record<string, any> = {
|
|
|
1045
1086
|
# Model field
|
|
1046
1087
|
model_id = spec.get("model")
|
|
1047
1088
|
model_ts = f"'{model_id}'" if model_id else "undefined"
|
|
1089
|
+
model_additionals = spec.get("model_additionals") or []
|
|
1090
|
+
model_additionals_ts_line = (
|
|
1091
|
+
f" modelAdditionals: {_fmt_ts_literal(model_additionals)},\n"
|
|
1092
|
+
if model_additionals
|
|
1093
|
+
else ""
|
|
1094
|
+
)
|
|
1048
1095
|
inference_provider = spec.get("inference_provider")
|
|
1049
1096
|
inference_provider_ts = (
|
|
1050
1097
|
f"'{inference_provider}'" if inference_provider else "undefined"
|
|
@@ -1116,7 +1163,7 @@ const FRONTEND_TOOL_MAP: Record<string, any> = {
|
|
|
1116
1163
|
domain: {domain_ts},
|
|
1117
1164
|
enabled: {str(spec.get("enabled", True)).lower()},
|
|
1118
1165
|
model: {model_ts},
|
|
1119
|
-
{inference_provider_line} mcpServers: [{mcp_servers_str}],
|
|
1166
|
+
{model_additionals_ts_line}{inference_provider_line} mcpServers: [{mcp_servers_str}],
|
|
1120
1167
|
skills: [{skills_str}].filter(Boolean) as SkillSpec[],
|
|
1121
1168
|
tools: [{tools_str}],
|
|
1122
1169
|
{disable_tool_approvals_line} frontendTools: [{frontend_tools_str}],
|
|
@@ -1588,6 +1635,7 @@ export * from './tracks';
|
|
|
1588
1635
|
export * from './ops';
|
|
1589
1636
|
export * from './apps';
|
|
1590
1637
|
export * from './actions';
|
|
1638
|
+
export * from './appspecSchema';
|
|
1591
1639
|
"""
|
|
1592
1640
|
)
|
|
1593
1641
|
|
|
@@ -17,6 +17,11 @@ read, write, send, buy, delete, publish — read from ``agentspecs.actions``.
|
|
|
17
17
|
Each application is validated here, by ``agentspecs`` itself, and arrives
|
|
18
18
|
with its layout said and with what it names that is not enabled (`setup`).
|
|
19
19
|
|
|
20
|
+
How each was built travels beside it (``APP_BUILT``), since an Appspec does
|
|
21
|
+
not hold it: ``python`` when ``apps/<id>/app.py`` sits beside its spec
|
|
22
|
+
(agentspecs >= 0.0.15), ``canvas`` when its page says it was composed on the
|
|
23
|
+
Canvas (``composed_by: canvas``, agentspecs >= 0.0.23), else ``written``.
|
|
24
|
+
|
|
20
25
|
Usage:
|
|
21
26
|
python generate_apps.py \\
|
|
22
27
|
--specs-dir agentspecs/agentspecs/apps \\
|
|
@@ -42,10 +47,11 @@ CAMEL = {
|
|
|
42
47
|
"ready_at": "readyAt",
|
|
43
48
|
"keep_for": "keepFor",
|
|
44
49
|
"retention_days": "retentionDays",
|
|
50
|
+
"suggest_tests": "suggestTests",
|
|
45
51
|
"composed_by": "composedBy",
|
|
46
52
|
"composed_at": "composedAt",
|
|
47
53
|
"min_confidence": "minConfidence",
|
|
48
|
-
"
|
|
54
|
+
"decision_model": "decisionModel",
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
#: The keys whose value is carried as it is written: a component tree, weights by name.
|
|
@@ -57,6 +63,16 @@ def _flat(text: Any) -> str:
|
|
|
57
63
|
return " ".join(str(text or "").split())
|
|
58
64
|
|
|
59
65
|
|
|
66
|
+
def built_of(specs_dir: Path, identity: str, app: Any) -> str:
|
|
67
|
+
"""How an application was built: `python`, `canvas` or `written`."""
|
|
68
|
+
if (specs_dir / identity / "app.py").is_file():
|
|
69
|
+
return "python"
|
|
70
|
+
surface = app.interface.surface
|
|
71
|
+
if surface is not None and surface.composed_by == "canvas":
|
|
72
|
+
return "canvas"
|
|
73
|
+
return "written"
|
|
74
|
+
|
|
75
|
+
|
|
60
76
|
def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
61
77
|
"""Every application, validated, as plain data — and the action classes."""
|
|
62
78
|
apps_module = import_from_clone(specs_dir, "apps")
|
|
@@ -66,6 +82,7 @@ def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
|
66
82
|
behaviours: dict[str, dict[str, str]] = {}
|
|
67
83
|
escalations: dict[str, dict[str, Any]] = {}
|
|
68
84
|
sources: dict[str, Any] = {}
|
|
85
|
+
built: dict[str, str] = {}
|
|
69
86
|
for identity in sorted(apps):
|
|
70
87
|
app = apps[identity]
|
|
71
88
|
spec = app.model_dump(mode="json", by_alias=True)
|
|
@@ -78,6 +95,7 @@ def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
|
78
95
|
}
|
|
79
96
|
escalations[identity] = dict(sorted(apps_module.tool_escalations(app).items()))
|
|
80
97
|
sources[identity] = apps_module.dump_app(app)
|
|
98
|
+
built[identity] = built_of(specs_dir, identity, app)
|
|
81
99
|
for rule in spec["rules"]:
|
|
82
100
|
rule["applies_to"] = (
|
|
83
101
|
[rule["applies_to"]]
|
|
@@ -129,6 +147,7 @@ def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
|
129
147
|
"behaviours": behaviours,
|
|
130
148
|
"escalations": escalations,
|
|
131
149
|
"sources": sources,
|
|
150
|
+
"built": built,
|
|
132
151
|
}
|
|
133
152
|
return specs, actions
|
|
134
153
|
|
|
@@ -174,7 +193,7 @@ ABOUT_ACTIONS = [
|
|
|
174
193
|
]
|
|
175
194
|
|
|
176
195
|
|
|
177
|
-
def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
196
|
+
def generate_python_code(specs: list[dict[str, Any]], built: dict[str, str]) -> str:
|
|
178
197
|
"""Generate the Python application catalogue."""
|
|
179
198
|
lines = [
|
|
180
199
|
"# Copyright (c) 2025-2026 Datalayer, Inc.",
|
|
@@ -185,7 +204,7 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
|
185
204
|
*HEADER,
|
|
186
205
|
'"""',
|
|
187
206
|
"",
|
|
188
|
-
"from typing import Dict",
|
|
207
|
+
"from typing import Dict, Literal",
|
|
189
208
|
"",
|
|
190
209
|
"from agent_runtimes.types import AppSpec",
|
|
191
210
|
"",
|
|
@@ -215,6 +234,10 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
|
215
234
|
[
|
|
216
235
|
"}",
|
|
217
236
|
"",
|
|
237
|
+
"#: How each application was built: `python` (its `app.py`), `canvas` (its",
|
|
238
|
+
"#: page composed on the Canvas) or `written` (its spec written out).",
|
|
239
|
+
f"APP_BUILT: Dict[str, Literal['python', 'canvas', 'written']] = {built!r}",
|
|
240
|
+
"",
|
|
218
241
|
"",
|
|
219
242
|
"def get_app(app_id: str) -> AppSpec | None:",
|
|
220
243
|
' """An application, by `id` or `id:version`, or None."""',
|
|
@@ -235,7 +258,7 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
|
235
258
|
|
|
236
259
|
|
|
237
260
|
def generate_typescript_code(
|
|
238
|
-
specs: list[dict[str, Any]], sources: dict[str, Any]
|
|
261
|
+
specs: list[dict[str, Any]], sources: dict[str, Any], built: dict[str, str]
|
|
239
262
|
) -> str:
|
|
240
263
|
"""Generate the TypeScript application catalogue."""
|
|
241
264
|
lines = [
|
|
@@ -250,7 +273,7 @@ def generate_typescript_code(
|
|
|
250
273
|
*[f" * {line}" for line in HEADER],
|
|
251
274
|
" */",
|
|
252
275
|
"",
|
|
253
|
-
"import type { AppKind, AppSpec } from '../types/agentspecs';",
|
|
276
|
+
"import type { AppBuilt, AppKind, AppSpec } from '../types/agentspecs';",
|
|
254
277
|
"",
|
|
255
278
|
]
|
|
256
279
|
for spec in specs:
|
|
@@ -288,6 +311,14 @@ def generate_typescript_code(
|
|
|
288
311
|
+ json.dumps(sources, indent=2, ensure_ascii=False)
|
|
289
312
|
+ ";",
|
|
290
313
|
"",
|
|
314
|
+
"/**",
|
|
315
|
+
" * How each application was built: `python` (its `app.py`), `canvas` (its",
|
|
316
|
+
" * page composed on the Canvas) or `written` (its spec written out).",
|
|
317
|
+
" */",
|
|
318
|
+
"export const APP_BUILT: Record<string, AppBuilt> = "
|
|
319
|
+
+ json.dumps(built, indent=2)
|
|
320
|
+
+ ";",
|
|
321
|
+
"",
|
|
291
322
|
"/** Every application of the catalogue, or those of a kind. */",
|
|
292
323
|
"export function listApps(kind?: AppKind): AppSpec[] {",
|
|
293
324
|
" return Object.values(APP_CATALOGUE).filter(",
|
|
@@ -475,10 +506,10 @@ def main() -> None:
|
|
|
475
506
|
sys.exit(1)
|
|
476
507
|
specs, actions = load_specs(args.specs_dir)
|
|
477
508
|
outputs = [
|
|
478
|
-
(args.python_output, generate_python_code(specs)),
|
|
509
|
+
(args.python_output, generate_python_code(specs, actions["built"])),
|
|
479
510
|
(
|
|
480
511
|
args.typescript_output,
|
|
481
|
-
generate_typescript_code(specs, actions["sources"]),
|
|
512
|
+
generate_typescript_code(specs, actions["sources"], actions["built"]),
|
|
482
513
|
),
|
|
483
514
|
(args.actions_python_output, generate_actions_python_code(actions)),
|
|
484
515
|
(args.actions_typescript_output, generate_actions_typescript_code(actions)),
|
|
@@ -327,11 +327,11 @@ def generate_python_code(specs: list[dict[str, Any]]) -> str:
|
|
|
327
327
|
"",
|
|
328
328
|
"",
|
|
329
329
|
"def is_chat_model(model: AIModel) -> bool:",
|
|
330
|
-
' """A model a chat can run on: not a typed-
|
|
330
|
+
' """A model a chat can run on: not a typed-decision model (Jev), which',
|
|
331
331
|
" answers typed questions about a state and nothing else. A spec that",
|
|
332
332
|
" states no capability is read as a chat model.",
|
|
333
333
|
' """',
|
|
334
|
-
' return "
|
|
334
|
+
' return "decisions" not in model.capabilities',
|
|
335
335
|
"",
|
|
336
336
|
"",
|
|
337
337
|
"def list_chat_models() -> list[AIModel]:",
|
|
@@ -504,12 +504,12 @@ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
|
|
|
504
504
|
"}",
|
|
505
505
|
"",
|
|
506
506
|
"/**",
|
|
507
|
-
" * A model a chat can run on: not a typed-
|
|
507
|
+
" * A model a chat can run on: not a typed-decision model (Jev), which",
|
|
508
508
|
" * answers typed questions about a state and nothing else. A spec that",
|
|
509
509
|
" * states no capability is read as a chat model.",
|
|
510
510
|
" */",
|
|
511
511
|
"export function isChatModel(model: AIModel): boolean {",
|
|
512
|
-
" return !(model.capabilities ?? []).includes('
|
|
512
|
+
" return !(model.capabilities ?? []).includes('decisions');",
|
|
513
513
|
"}",
|
|
514
514
|
"",
|
|
515
515
|
"/** The models a person choosing a chat model is offered from. */",
|