@animalabs/agent-framework 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/src/code-execution/index.d.ts +14 -0
  2. package/dist/src/code-execution/index.d.ts.map +1 -0
  3. package/dist/src/code-execution/index.js +13 -0
  4. package/dist/src/code-execution/index.js.map +1 -0
  5. package/dist/src/code-execution/py-runner.d.ts +91 -0
  6. package/dist/src/code-execution/py-runner.d.ts.map +1 -0
  7. package/dist/src/code-execution/py-runner.js +364 -0
  8. package/dist/src/code-execution/py-runner.js.map +1 -0
  9. package/dist/src/code-execution/runtime-py.d.ts +32 -0
  10. package/dist/src/code-execution/runtime-py.d.ts.map +1 -0
  11. package/dist/src/code-execution/runtime-py.js +265 -0
  12. package/dist/src/code-execution/runtime-py.js.map +1 -0
  13. package/dist/src/code-execution/tool-definition.d.ts +16 -0
  14. package/dist/src/code-execution/tool-definition.d.ts.map +1 -0
  15. package/dist/src/code-execution/tool-definition.js +45 -0
  16. package/dist/src/code-execution/tool-definition.js.map +1 -0
  17. package/dist/src/framework.d.ts +43 -1
  18. package/dist/src/framework.d.ts.map +1 -1
  19. package/dist/src/framework.js +274 -26
  20. package/dist/src/framework.js.map +1 -1
  21. package/dist/src/index.d.ts +2 -0
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/index.js +4 -0
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/mcpl/channel-registry.d.ts +12 -0
  26. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  27. package/dist/src/mcpl/channel-registry.js +45 -0
  28. package/dist/src/mcpl/channel-registry.js.map +1 -1
  29. package/dist/src/mcpl/prose-grammar.d.ts +24 -0
  30. package/dist/src/mcpl/prose-grammar.d.ts.map +1 -0
  31. package/dist/src/mcpl/prose-grammar.js +33 -0
  32. package/dist/src/mcpl/prose-grammar.js.map +1 -0
  33. package/dist/src/mcpl/prose-stream-router.d.ts +58 -0
  34. package/dist/src/mcpl/prose-stream-router.d.ts.map +1 -0
  35. package/dist/src/mcpl/prose-stream-router.js +168 -0
  36. package/dist/src/mcpl/prose-stream-router.js.map +1 -0
  37. package/dist/src/types/framework.d.ts +33 -0
  38. package/dist/src/types/framework.d.ts.map +1 -1
  39. package/dist/test/code-execution.test.d.ts +11 -0
  40. package/dist/test/code-execution.test.d.ts.map +1 -0
  41. package/dist/test/code-execution.test.js +412 -0
  42. package/dist/test/code-execution.test.js.map +1 -0
  43. package/dist/test/prose-stream-router.test.d.ts +2 -0
  44. package/dist/test/prose-stream-router.test.d.ts.map +1 -0
  45. package/dist/test/prose-stream-router.test.js +87 -0
  46. package/dist/test/prose-stream-router.test.js.map +1 -0
  47. package/dist/tsconfig.tsbuildinfo +1 -1
  48. package/package.json +1 -1
  49. package/src/code-execution/index.ts +19 -0
  50. package/src/code-execution/py-runner.ts +433 -0
  51. package/src/code-execution/runtime-py.ts +265 -0
  52. package/src/code-execution/tool-definition.ts +52 -0
  53. package/src/framework.ts +307 -28
  54. package/src/index.ts +17 -0
  55. package/src/mcpl/channel-registry.ts +53 -0
  56. package/src/mcpl/prose-grammar.ts +40 -0
  57. package/src/mcpl/prose-stream-router.ts +161 -0
  58. package/src/types/framework.ts +35 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Incremental prose router: turns a stream of text deltas into a stream of
3
+ * (channelId, delta) pairs AS THE MODEL GENERATES, so downstream surfaces
4
+ * (voice synthesis, streamed message rendering, future platforms) can act on
5
+ * speech before the turn completes. This is the emission half of MCPL Spec
6
+ * 14.3 (`channels/outgoing/chunk`) — the notification has existed in the
7
+ * wire types since the spec landed; this class is what finally produces it.
8
+ *
9
+ * Correctness contract: routing decisions here MUST agree with the delivery
10
+ * path (deliverProse/parseProsePrefix). Both use prose-grammar.ts; this
11
+ * router additionally holds back exactly as much text as needed to make each
12
+ * line's decision — the start of a line is buffered only until it either
13
+ * cannot be a `>>` prefix (flush as body) or its prefix token (+ optional
14
+ * ` !` modifier) completes (switch target, swallow the prefix). Body text
15
+ * after the decision point streams through with zero added latency.
16
+ *
17
+ * Suppression is fail-closed: text whose destination is unknown (explicit
18
+ * mode before the first `>>`, unresolvable/ambiguous targets, `>>skip_reply`)
19
+ * emits NOTHING — the delivery path will bounce/retain it; a stream consumer
20
+ * must never see text that delivery would refuse to send.
21
+ *
22
+ * Two modes, mirroring proseRouting:
23
+ * - explicit: parse `>>` prefixes; initial target null (suppressed).
24
+ * - locus: NO prefix parsing (locus mode's `>>` lines are plain text);
25
+ * everything streams to the frozen turn locus.
26
+ */
27
+
28
+ export interface RoutedDelta {
29
+ channelId: string;
30
+ delta: string;
31
+ }
32
+
33
+ type State = 'line-start' | 'maybe-prefix' | 'prefix-token' | 'after-token' | 'body';
34
+
35
+ export class ProseStreamRouter {
36
+ private target: string | null;
37
+ private state: State = 'line-start';
38
+ private held = ''; // text held while a line's decision is pending
39
+ private token = ''; // the >>spec token being accumulated
40
+ private sawBang = false;
41
+ private accumulated = new Map<string, string>();
42
+
43
+ constructor(
44
+ private opts: {
45
+ /** 'explicit' parses `>>` prefixes; 'locus' is pure passthrough. */
46
+ mode: 'explicit' | 'locus';
47
+ /** Initial destination: the frozen turn locus (locus mode) or null. */
48
+ initialTarget: string | null;
49
+ /** Resolve a `>>spec` to a channelId, or null (unresolved/ambiguous). */
50
+ resolve: (spec: string) => string | null;
51
+ },
52
+ ) {
53
+ this.target = opts.initialTarget;
54
+ if (opts.mode === 'locus') this.state = 'body';
55
+ }
56
+
57
+ /** Per-channel full text emitted so far (for outgoing/complete). */
58
+ byChannel(): ReadonlyMap<string, string> {
59
+ return this.accumulated;
60
+ }
61
+
62
+ feed(delta: string): RoutedDelta[] {
63
+ const out: RoutedDelta[] = [];
64
+ for (const ch of delta) this.step(ch, out);
65
+ return coalesce(out);
66
+ }
67
+
68
+ /** End of stream: flush any held line-start text (it can no longer become a
69
+ * complete prefix; an unterminated `>>tok` at EOF is routing metadata with
70
+ * no body — dropped, matching delivery which would deliver empty body). */
71
+ finish(): RoutedDelta[] {
72
+ const out: RoutedDelta[] = [];
73
+ if (this.state === 'maybe-prefix') this.emit(this.held, out); // "" | ">" | ">>"
74
+ // prefix-token / after-token at EOF: prefix without body — nothing to emit.
75
+ this.held = '';
76
+ this.state = 'line-start';
77
+ return coalesce(out);
78
+ }
79
+
80
+ private step(ch: string, out: RoutedDelta[]): void {
81
+ switch (this.state) {
82
+ case 'body':
83
+ if (ch === '\n' && this.opts.mode === 'explicit') {
84
+ this.emit('\n', out);
85
+ this.state = 'line-start';
86
+ } else {
87
+ this.emit(ch, out);
88
+ }
89
+ return;
90
+
91
+ case 'line-start':
92
+ if (ch === '>') { this.state = 'maybe-prefix'; this.held = '>'; return; }
93
+ this.state = 'body';
94
+ this.step(ch, out);
95
+ return;
96
+
97
+ case 'maybe-prefix':
98
+ if (this.held === '>' && ch === '>') { this.held = '>>'; return; }
99
+ if (this.held === '>>') {
100
+ if (ch === '\n' || ch === ' ' || ch === '\t') {
101
+ // `>> quoted arrow` / bare `>>`: body text, not a prefix.
102
+ this.emit(this.held, out);
103
+ this.held = '';
104
+ this.state = 'body';
105
+ this.step(ch, out);
106
+ return;
107
+ }
108
+ // real prefix begins
109
+ this.state = 'prefix-token';
110
+ this.token = ch;
111
+ this.held = '';
112
+ return;
113
+ }
114
+ // held === '>' and ch !== '>': plain body starting with '>'
115
+ this.emit(this.held, out);
116
+ this.held = '';
117
+ this.state = 'body';
118
+ this.step(ch, out);
119
+ return;
120
+
121
+ case 'prefix-token':
122
+ if (ch === ' ' || ch === '\t') { this.applyTarget(); this.state = 'after-token'; this.sawBang = false; return; }
123
+ if (ch === '\n') { this.applyTarget(); this.state = 'line-start'; return; }
124
+ this.token += ch;
125
+ return;
126
+
127
+ case 'after-token':
128
+ // Grammar: `([ \t]+!)?[ \t]*` — one optional `!` immediately after the
129
+ // first whitespace run is the continue-turn modifier, not body.
130
+ if (ch === ' ' || ch === '\t') return;
131
+ if (ch === '\n') { this.state = 'line-start'; return; }
132
+ if (ch === '!' && !this.sawBang) { this.sawBang = true; return; }
133
+ this.state = 'body';
134
+ this.step(ch, out);
135
+ return;
136
+ }
137
+ }
138
+
139
+ private applyTarget(): void {
140
+ const spec = this.token;
141
+ this.token = '';
142
+ if (spec === 'skip_reply') { this.target = null; return; }
143
+ this.target = this.opts.resolve(spec);
144
+ }
145
+
146
+ private emit(text: string, out: RoutedDelta[]): void {
147
+ if (this.target === null || !text) return;
148
+ out.push({ channelId: this.target, delta: text });
149
+ this.accumulated.set(this.target, (this.accumulated.get(this.target) ?? '') + text);
150
+ }
151
+ }
152
+
153
+ function coalesce(deltas: RoutedDelta[]): RoutedDelta[] {
154
+ const out: RoutedDelta[] = [];
155
+ for (const d of deltas) {
156
+ const last = out[out.length - 1];
157
+ if (last && last.channelId === d.channelId) last.delta += d.delta;
158
+ else out.push({ ...d });
159
+ }
160
+ return out;
161
+ }
@@ -31,6 +31,35 @@ export interface ProcessLoggingConfig {
31
31
  /**
32
32
  * Configuration for the agent framework.
33
33
  */
34
+ /**
35
+ * Client-side programmatic tool calling: a synthesized `code_execution` tool
36
+ * that runs model-authored Python on the host, with every framework tool
37
+ * injected as an async function. Mirrors the trained surface of Anthropic's
38
+ * server-side PTC; inner tool results never enter the model context.
39
+ *
40
+ * Robustness boundary, not a security sandbox (same doctrine as gate.js —
41
+ * the agent already has broader host access through its tools).
42
+ */
43
+ export interface CodeExecutionConfig {
44
+ /** Master switch — the tool is only synthesized when true. */
45
+ enabled: boolean;
46
+ /** Python interpreter to spawn (default: 'python3' from PATH). */
47
+ pythonPath?: string;
48
+ /**
49
+ * Per-inner-tool-call timeout; exceeding it raises TimeoutError inside the
50
+ * script (default 270_000 ms, mirroring the managed runtime's message).
51
+ */
52
+ toolCallTimeoutMs?: number;
53
+ /** Whole-script deadline: cancel → grace → SIGKILL (default 600_000 ms). */
54
+ scriptTimeoutMs?: number;
55
+ /**
56
+ * Idle interpreter reclaim — script globals are lost after this much
57
+ * inactivity (default 300_000 ms, mirroring ~5-minute container reclaim).
58
+ * 0 disables reclaim.
59
+ */
60
+ idleReclaimMs?: number;
61
+ }
62
+
34
63
  export interface FrameworkConfig {
35
64
  /**
36
65
  * IANA zone used only when rendering wall-clock times for the agent.
@@ -93,6 +122,12 @@ export interface FrameworkConfig {
93
122
  /** MCPL server configurations. If omitted or empty, no MCPL subsystems are created. */
94
123
  mcplServers?: McplServerConfig[];
95
124
 
125
+ /**
126
+ * Client-side programmatic tool calling (`code_execution` tool). Omitted or
127
+ * `enabled: false` → tool absent, no python is ever spawned.
128
+ */
129
+ codeExecution?: CodeExecutionConfig;
130
+
96
131
  /** Inference routing policy for server-initiated inference (optional). */
97
132
  inferenceRouting?: InferenceRoutingPolicy;
98
133