@ziggs-ai/agent-sdk 0.1.3 → 0.1.4

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 (85) hide show
  1. package/README.md +1 -1
  2. package/package.json +9 -4
  3. package/src/AgentHost.ts +342 -0
  4. package/src/adapters/OpenAIAdapter.ts +125 -0
  5. package/src/agent/Agent.ts +98 -0
  6. package/src/cognition/validateContext.ts +95 -0
  7. package/src/context/applyEffects.ts +80 -0
  8. package/src/context/batch.ts +17 -0
  9. package/src/context/classifyEnvelope.ts +38 -0
  10. package/src/context/routingLabels.ts +46 -0
  11. package/src/defineAgent.ts +62 -0
  12. package/src/formatters/AgreementFormatter.ts +111 -0
  13. package/src/formatters/HistoryFormatter.ts +166 -0
  14. package/src/formatters/index.ts +2 -0
  15. package/src/index.ts +86 -0
  16. package/src/ingress/normalizeIncoming.ts +119 -0
  17. package/src/memory/MemoryStore.ts +104 -0
  18. package/src/runtime/AgentMachine.ts +298 -0
  19. package/src/runtime/PromptBuilder.ts +461 -0
  20. package/src/runtime/buildOutcome.ts +488 -0
  21. package/src/runtime/defaults.ts +72 -0
  22. package/src/runtime/runTurn.ts +637 -0
  23. package/src/runtime/validateWorkflow.ts +165 -0
  24. package/src/server/ConnectionPool.ts +155 -0
  25. package/src/server/EventQueue.ts +119 -0
  26. package/src/server/OutboxBuffer.ts +90 -0
  27. package/src/server/ZiggsEffectHandler.ts +335 -0
  28. package/src/server/agreements/AgreementService.ts +111 -0
  29. package/src/server/createHealthServer.ts +8 -0
  30. package/src/server/proactive/ProactiveTrigger.ts +83 -0
  31. package/src/server/runLauncher.ts +131 -0
  32. package/src/server/tasks/TaskService.ts +111 -0
  33. package/src/server/tasks/index.ts +4 -0
  34. package/src/server/tasks/paymentTools.ts +156 -0
  35. package/src/server/tasks/protocolRunner.ts +101 -0
  36. package/src/server/tasks/protocolTools.ts +96 -0
  37. package/src/server/ziggspay/ZiggsPayClient.ts +193 -0
  38. package/src/shared/ids.ts +3 -0
  39. package/src/shared/runtimeLog.ts +72 -0
  40. package/src/shared/types.ts +31 -0
  41. package/src/tasks/protocolRegistry.ts +25 -0
  42. package/src/tasks/taskCore.ts +139 -0
  43. package/src/tools/ToolManager.ts +95 -0
  44. package/src/tools/{ToolProvider.js → ToolProvider.ts} +5 -15
  45. package/src/tools/defineTool.ts +90 -0
  46. package/src/tools/index.ts +5 -0
  47. package/src/types.ts +368 -0
  48. package/src/utils/jsonExtractor.ts +100 -0
  49. package/src/ConnectionPool.js +0 -133
  50. package/src/adapters/OpenAIAdapter.js +0 -73
  51. package/src/agent/Agent.js +0 -121
  52. package/src/agent/EventQueue.js +0 -68
  53. package/src/agent/OutboxBuffer.js +0 -62
  54. package/src/cognition/PromptBuilder.js +0 -312
  55. package/src/cognition/resolveActionTool.js +0 -12
  56. package/src/cognition/runTurn.js +0 -578
  57. package/src/context/applyEffects.js +0 -133
  58. package/src/context/batch.js +0 -25
  59. package/src/context/classifyEnvelope.js +0 -82
  60. package/src/context/routingLabels.js +0 -54
  61. package/src/createHealthServer.js +0 -28
  62. package/src/formatters/HistoryFormatter.js +0 -257
  63. package/src/formatters/TaskFormatter.js +0 -180
  64. package/src/formatters/index.js +0 -9
  65. package/src/index.js +0 -76
  66. package/src/ingress/normalizeIncoming.js +0 -70
  67. package/src/runLauncher.js +0 -159
  68. package/src/shared/ids.js +0 -7
  69. package/src/shared/types.js +0 -86
  70. package/src/tasks/TaskService.js +0 -247
  71. package/src/tasks/index.js +0 -9
  72. package/src/tasks/taskCore.js +0 -229
  73. package/src/tasks/taskProtocolRegistry.js +0 -22
  74. package/src/tasks/taskProtocolRunner.js +0 -107
  75. package/src/tasks/taskProtocolTools.js +0 -87
  76. package/src/tools/ToolManager.js +0 -79
  77. package/src/tools/defineTool.js +0 -82
  78. package/src/tools/index.js +0 -11
  79. package/src/utils/jsonExtractor.js +0 -139
  80. package/src/workflow/AgentMachine.js +0 -250
  81. package/src/workflow/WorkflowRuntime.js +0 -63
  82. package/src/workflow/dsl.js +0 -287
  83. package/src/workflow/motifs.js +0 -435
  84. package/src/ziggs/runtime.js +0 -192
  85. /package/src/adapters/{index.js → index.ts} +0 -0
package/src/types.ts ADDED
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Foundation types for the agent SDK workflow runtime.
3
+ *
4
+ * One outcome vocabulary, one ctx shape, one transition signature.
5
+ * Every state — parked or thinking — re-enters with exactly one Outcome.
6
+ */
7
+
8
+ // ── Refs (opaque payloads from the protocol layer) ───────────────────────────
9
+
10
+ export interface AgreementRef {
11
+ agreementId: string;
12
+ parties?: {
13
+ creatorId?: string;
14
+ providerId?: string;
15
+ payerId?: string;
16
+ proposedToId?: string;
17
+ };
18
+ proposal?: { status?: 'pending' | 'approved' | 'rejected' | 'cancelled' };
19
+ money?: { price?: number; paymentStatus?: string };
20
+ }
21
+
22
+ export interface TaskRef {
23
+ taskId: string;
24
+ agreementId?: string;
25
+ agreement?: AgreementRef;
26
+ state?: string;
27
+ description?: string;
28
+ parentTaskId?: string;
29
+ proposal?: { status?: 'pending' | 'approved' | 'rejected' | 'cancelled' };
30
+ proposedToIsYou?: boolean;
31
+ providerIsYou?: boolean;
32
+ creatorIsYou?: boolean;
33
+ payerIsYou?: boolean;
34
+ }
35
+
36
+ export interface ProposalRef {
37
+ agreementId: string;
38
+ agreement?: AgreementRef;
39
+ }
40
+
41
+ // ── Outcomes (hybrid: closed core + extension) ───────────────────────────────
42
+
43
+ export type OutcomeKind =
44
+ | 'enter'
45
+ | 'wait'
46
+ | 'timeout'
47
+ | 'message-sent'
48
+ | 'message-received'
49
+ | 'task-assigned'
50
+ | 'task-delegated'
51
+ | 'task-closed'
52
+ | 'subtask-finished'
53
+ | 'proposal-made'
54
+ | 'proposal-resolved'
55
+ | 'tool-result'
56
+ | 'error'
57
+ | 'extension';
58
+
59
+ export type Outcome =
60
+ | { kind: 'enter' }
61
+ | { kind: 'wait' }
62
+ | { kind: 'timeout' }
63
+ | { kind: 'message-sent'; text?: string; receiverId?: string }
64
+ | { kind: 'message-received'; text?: string; senderId?: string }
65
+ | { kind: 'task-assigned'; task: TaskRef }
66
+ | { kind: 'task-delegated'; task: TaskRef }
67
+ | { kind: 'task-closed'; status: 'completed' | 'failed'; result?: unknown }
68
+ | { kind: 'subtask-finished'; status: 'completed' | 'failed'; task: TaskRef; result?: unknown }
69
+ | { kind: 'proposal-made'; proposal: ProposalRef }
70
+ | { kind: 'proposal-resolved'; action: 'approve' | 'reject'; proposal: ProposalRef }
71
+ | { kind: 'tool-result'; tool: string; result: unknown }
72
+ | {
73
+ kind: 'error';
74
+ source: 'llm' | 'tool' | 'protocol' | 'validation' | 'unknown';
75
+ cause: unknown;
76
+ retryable: boolean;
77
+ }
78
+ | { kind: 'extension'; name: string; payload: unknown };
79
+
80
+ // ── Ctx (memory only — no event-derived flags) ───────────────────────────────
81
+
82
+ /**
83
+ * A session is the Server-side abstraction the Agent operates against:
84
+ * `{ sessionId, ctx, queue, parked }`. `Identity` is the Agent's view of
85
+ * which session this tick belongs to. `sessionId` is opaque to the Agent —
86
+ * it's a string the Server uses to route inbound events and outbound
87
+ * effects. (Today, the Ziggs transport derives sessionId from `chatId` at
88
+ * the wire boundary; non-chat surfaces will produce sessionIds of other
89
+ * shapes.)
90
+ */
91
+ export interface Identity {
92
+ agentId: string;
93
+ sessionId: string;
94
+ laneKey: string;
95
+ }
96
+
97
+ export interface HistoryEntry {
98
+ entryType?: string;
99
+ text?: string;
100
+ sender?: { id?: string; type?: string };
101
+ receiver?: { id?: string };
102
+ timestamp?: number;
103
+ [key: string]: unknown;
104
+ }
105
+
106
+ export interface ToolResultEntry {
107
+ tool: string;
108
+ result?: unknown;
109
+ error?: string;
110
+ }
111
+
112
+ /**
113
+ * Per-lane runtime state. Outcome is "what just happened"; Ctx is "what I know."
114
+ *
115
+ * Persistent identifiers (activeAgreementId, pendingProposalAgreementId,
116
+ * delegatedTaskIds) survive across outcomes — they're the memory of which
117
+ * agreement the agent is currently bound to and which subtasks it's awaiting.
118
+ */
119
+ export interface Ctx {
120
+ identity: Identity;
121
+
122
+ // Persistent protocol state
123
+ activeAgreementId: string | null;
124
+ activeTaskId: string | null;
125
+ pendingProposalAgreementId: string | null;
126
+ delegatedAgreementIds: string[];
127
+ delegatedTaskIds: string[];
128
+
129
+ // Per-turn working memory
130
+ toolResults: ToolResultEntry[];
131
+
132
+ // Last event/turn that caused this entry
133
+ lastOutcome: Outcome;
134
+
135
+ // Server-provided context (history, agreements, agents, users) is fetched
136
+ // on demand inside runTurn; not persisted on Ctx.
137
+ }
138
+
139
+ // ── Workflow shape ───────────────────────────────────────────────────────────
140
+
141
+ export interface PromptDef {
142
+ role?: string;
143
+ goal?: string;
144
+ context?: string;
145
+ constraints?: string;
146
+ }
147
+
148
+ export interface ActionPrompt {
149
+ instruction: string;
150
+ format?: string;
151
+ when?: string;
152
+ examples?: Array<{ scenario?: string; input?: string; output?: string; query?: string }>;
153
+ }
154
+
155
+ /**
156
+ * `produces` is the outcome-kind contract for an action. Static form for
157
+ * actions that always emit the same kind (most common); function form for
158
+ * tool-bound actions where the result determines the kind (e.g. success vs
159
+ * error). The runtime composes the kind with action metadata to build the
160
+ * full Outcome.
161
+ */
162
+ export type ProducesFn = (result: unknown, args: unknown) => OutcomeKind;
163
+ export type Produces = OutcomeKind | ProducesFn;
164
+
165
+ export interface Action {
166
+ prompt: ActionPrompt;
167
+ tool?: string | null;
168
+ produces: Produces;
169
+ }
170
+
171
+ export interface Transition {
172
+ to: string;
173
+ when?: (outcome: Outcome, ctx: Ctx) => boolean;
174
+ }
175
+
176
+ export interface ParkedState {
177
+ kind: 'parked';
178
+ transitions: Transition[];
179
+ }
180
+
181
+ export interface ThinkingState {
182
+ kind: 'thinking';
183
+ prompt: PromptDef;
184
+ actions: Record<string, Action>;
185
+ transitions: Transition[];
186
+ /**
187
+ * Opt out of the validateWorkflow warning that flags thinking states
188
+ * missing a `wait` action / activeWait fallthrough. Use sparingly — silent
189
+ * states that loop on themselves are usually a bug.
190
+ */
191
+ allowNoWait?: boolean;
192
+ }
193
+
194
+ export type State = ParkedState | ThinkingState;
195
+
196
+ export interface Workflow {
197
+ id: string;
198
+ description: string;
199
+ initial: string;
200
+ states: Record<string, State>;
201
+ }
202
+
203
+ // ── Effects (the Agent ↔ Server boundary) ────────────────────────────────────
204
+ //
205
+ // The Agent is pure: its only side channel is `EffectHandler`. Every impure
206
+ // operation — reading context, calling the LLM, executing a tool, sending a
207
+ // message — is expressed as an Effect. The Server implements the handler;
208
+ // the Agent emits effects via `await eff({ kind: ..., ... })` and consumes
209
+ // the typed result. This is the Node.js-style "sync dress over async" loop:
210
+ // `tick` reads top-to-bottom but every `await` is a yield to the Server.
211
+
212
+ export interface LlmToolCall {
213
+ id: string;
214
+ function: { name: string; arguments: string };
215
+ }
216
+
217
+ export interface LlmMessage {
218
+ role: 'system' | 'user' | 'assistant' | 'tool';
219
+ content?: string;
220
+ tool_calls?: LlmToolCall[];
221
+ tool_call_id?: string;
222
+ }
223
+
224
+ export interface LlmToolSchema {
225
+ type: 'function';
226
+ function: { name: string; description?: string; parameters?: unknown };
227
+ }
228
+
229
+ export interface LlmResponse {
230
+ content?: string;
231
+ tool_calls?: LlmToolCall[];
232
+ usage?: {
233
+ total_tokens?: number;
234
+ prompt_tokens?: number;
235
+ completion_tokens?: number;
236
+ totalTokens?: number;
237
+ promptTokens?: number;
238
+ completionTokens?: number;
239
+ };
240
+ }
241
+
242
+ export interface ContextSnapshot {
243
+ history: HistoryEntry[];
244
+ agreements: unknown[];
245
+ agents: unknown[];
246
+ users: unknown[];
247
+ timelineSummary?: string | null;
248
+ latestSequence?: string | null;
249
+ unavailable?: boolean;
250
+ /** Index signature so PromptBuilder's `ServerContext` can accept it directly. */
251
+ [key: string]: unknown;
252
+ }
253
+
254
+ export interface ReadContextOpts {
255
+ maxMessages?: number;
256
+ maxCharacters?: number;
257
+ maxTaskHistory?: number;
258
+ summarize?: boolean;
259
+ sinceSequence?: string;
260
+ }
261
+
262
+ /**
263
+ * Result of executing a single tool. Tool execution may emit multiple
264
+ * "events" (terminal markers like `message_sent`, `waited`, etc.) that the
265
+ * cognition loop inspects to decide whether to keep looping. Those carry
266
+ * through here as `events`; `result` is the value returned to the LLM.
267
+ */
268
+ export interface ToolCallResult {
269
+ ok: boolean;
270
+ result?: unknown;
271
+ error?: string;
272
+ events?: unknown[];
273
+ }
274
+
275
+ export interface RecordedEntry {
276
+ kind: string;
277
+ text?: string;
278
+ [k: string]: unknown;
279
+ }
280
+
281
+ import type { MemoryStore } from './memory/MemoryStore.js';
282
+
283
+ export type EffectKind =
284
+ | 'read-context'
285
+ | 'llm-call'
286
+ | 'tool-call'
287
+ | 'send-message'
288
+ | 'record-event'
289
+ | 'get-scope'
290
+ | 'list-messages'
291
+ | 'list-artifacts'
292
+ | 'list-task-history';
293
+
294
+ export interface ScopeRef { kind: 'chat' | 'agreement' | 'task' | 'counterparty'; id: string }
295
+
296
+ export type Effect =
297
+ | { kind: 'read-context'; sessionId: string; opts?: ReadContextOpts }
298
+ | { kind: 'llm-call'; messages: LlmMessage[]; tools: LlmToolSchema[] }
299
+ | { kind: 'tool-call'; sessionId: string; name: string; args: unknown; memory?: MemoryStore }
300
+ | { kind: 'send-message'; sessionId: string; receiverId: string; text: string }
301
+ | { kind: 'record-event'; sessionId: string; entry: RecordedEntry }
302
+ | { kind: 'get-scope'; via: ScopeRef }
303
+ | { kind: 'list-messages'; chatId: string; after?: string; limit?: number }
304
+ | { kind: 'list-artifacts'; chatId?: string; agreementId?: string; after?: string; limit?: number }
305
+ | { kind: 'list-task-history'; taskId: string; after?: string; limit?: number };
306
+
307
+ /**
308
+ * Typed result map. With this shape, `await eff({ kind: 'read-context', ... })`
309
+ * resolves to `ContextSnapshot`, not a union — the discriminant carries the
310
+ * result type. Tests and Server impls implement this map exhaustively.
311
+ */
312
+ export interface EffectResultMap {
313
+ 'read-context': ContextSnapshot;
314
+ 'llm-call': LlmResponse;
315
+ 'tool-call': ToolCallResult;
316
+ 'send-message': { ok: true };
317
+ 'record-event': { ok: true };
318
+ 'get-scope': unknown;
319
+ 'list-messages': { messages: unknown[]; latestSequence: string | null };
320
+ 'list-artifacts': { artifacts: unknown[]; latestSequence: string | null };
321
+ 'list-task-history': { taskId: string; history: unknown[]; latestSequence: string | null };
322
+ }
323
+
324
+ export type EffectResult<K extends EffectKind> = EffectResultMap[K];
325
+
326
+ /** The single side-channel between Agent and Server. */
327
+ export type EffectHandler = <K extends EffectKind>(
328
+ effect: Extract<Effect, { kind: K }>,
329
+ ) => Promise<EffectResult<K>>;
330
+
331
+ // ── defineAgent input/output ─────────────────────────────────────────────────
332
+
333
+ export interface ServicesConfig {
334
+ llm?: { model: string; temperature?: number };
335
+ tools?: unknown[];
336
+ }
337
+
338
+ export interface DefineAgentInput {
339
+ agentId: string;
340
+ description: string;
341
+ specialization?: string;
342
+ initial: string;
343
+ states: Record<string, State>;
344
+ tools?: unknown[];
345
+ services?: ServicesConfig;
346
+ operatorKey?: string;
347
+ taskTools?: 'all' | 'none' | string[];
348
+ [key: string]: unknown;
349
+ }
350
+
351
+ /**
352
+ * Frozen output of defineAgent. Consumed directly by AgentHost / createAgent.
353
+ * No mutation — defineAgent validates and returns; injection of defaults is
354
+ * the workflow author's responsibility (visible spread of `thinkingDefaults`).
355
+ */
356
+ export interface AgentConfig {
357
+ openaiKey: string | undefined;
358
+ operatorKey?: string;
359
+ agentId: string;
360
+ description: string;
361
+ specialization?: string;
362
+ tools: unknown[];
363
+ model: string;
364
+ workflow: Workflow;
365
+ services?: ServicesConfig;
366
+ taskTools?: 'all' | 'none' | string[];
367
+ [key: string]: unknown;
368
+ }
@@ -0,0 +1,100 @@
1
+ import { runtimeLog } from '../shared/runtimeLog.js';
2
+
3
+ export function extractJSON(content: string): string {
4
+ if (!content || typeof content !== 'string') return '';
5
+
6
+ const trimmed = content.trim();
7
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
8
+ const jsonBlockMatch = content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
9
+ if (jsonBlockMatch) {
10
+ content = jsonBlockMatch[1].trim();
11
+ }
12
+ }
13
+
14
+ const firstBrace = content.indexOf('{');
15
+ const firstBracket = content.indexOf('[');
16
+
17
+ let startIdx = -1;
18
+ let isArray = false;
19
+
20
+ if (firstBrace !== -1 && (firstBracket === -1 || firstBrace < firstBracket)) {
21
+ startIdx = firstBrace;
22
+ isArray = false;
23
+ } else if (firstBracket !== -1) {
24
+ startIdx = firstBracket;
25
+ isArray = true;
26
+ }
27
+
28
+ if (startIdx === -1) return content.trim();
29
+
30
+ const openChar = isArray ? '[' : '{';
31
+ const closeChar = isArray ? ']' : '}';
32
+ let depth = 0;
33
+ let endIdx = startIdx;
34
+
35
+ for (let i = startIdx; i < content.length; i++) {
36
+ const char = content[i];
37
+ if (char === openChar) {
38
+ depth++;
39
+ } else if (char === closeChar) {
40
+ depth--;
41
+ if (depth === 0) { endIdx = i + 1; break; }
42
+ } else if (char === '"') {
43
+ i++;
44
+ while (i < content.length && content[i] !== '"') {
45
+ if (content[i] === '\\') i++;
46
+ i++;
47
+ }
48
+ }
49
+ }
50
+
51
+ if (endIdx > startIdx) content = content.substring(startIdx, endIdx);
52
+
53
+ content = content.trim();
54
+ content = content.replace(/(?<!:)\/\/.*$/gm, '');
55
+ content = content.replace(/\/\*[\s\S]*?\*\//g, '');
56
+ content = content.replace(/,(\s*[}\]])/g, '$1');
57
+
58
+ return content;
59
+ }
60
+
61
+ function repairJsonStrings(jsonText: string): string {
62
+ if (!jsonText || typeof jsonText !== 'string') return jsonText;
63
+ const validEscapes = new Set(['"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u']);
64
+ let out = '';
65
+ let inString = false;
66
+
67
+ for (let i = 0; i < jsonText.length; i++) {
68
+ const ch = jsonText[i];
69
+ if (!inString) {
70
+ if (ch === '"') inString = true;
71
+ out += ch;
72
+ continue;
73
+ }
74
+ if (ch === '\\') {
75
+ const next = jsonText[i + 1];
76
+ out += (next && validEscapes.has(next)) ? ch : '\\\\';
77
+ continue;
78
+ }
79
+ if (ch === '"') { inString = false; out += ch; continue; }
80
+ if (ch === '\n') { out += '\\n'; continue; }
81
+ if (ch === '\r') { out += '\\r'; continue; }
82
+ out += ch;
83
+ }
84
+ return out;
85
+ }
86
+
87
+ export function safeParseJSON<T = unknown>(content: string, fallback: T | null = null): T | null {
88
+ try {
89
+ const extracted = extractJSON(content);
90
+ if (!extracted || extracted.trim().length === 0) throw new Error('Empty JSON content');
91
+ try {
92
+ return JSON.parse(extracted) as T;
93
+ } catch {
94
+ return JSON.parse(repairJsonStrings(extracted)) as T;
95
+ }
96
+ } catch {
97
+ runtimeLog.error('JSONExtractor', 'Error parsing JSON', content.length > 500 ? `${content.slice(0, 500)}…` : content);
98
+ return fallback ?? null;
99
+ }
100
+ }
@@ -1,133 +0,0 @@
1
- /**
2
- * ConnectionPool — ZiggsAgent-aware wrapper around api-client's ConnectionManager.
3
- *
4
- * The actual pool mechanics (LRU, idle timeout, control socket) live in
5
- * api-client and know nothing about agents. This class just registers each
6
- * `defineAgent` config with the manager, supplying the open/close functions
7
- * that instantiate and tear down a `ZiggsAgent`.
8
- *
9
- * Usage:
10
- * const pool = new ConnectionPool({ maxActive: 50, idleTimeoutMs: 60_000 });
11
- * pool.register(agentConfigs, agentMetadata);
12
- * pool.startControl({ wsUrl, operatorKey }); // optional, enables wake-on-demand
13
- * const agent = await pool.wake('agent-42');
14
- * await pool.sendTo('agent-42', text, metadata);
15
- */
16
-
17
- import { ConnectionManager } from '@ziggs-ai/api-client';
18
- import { ZiggsAgent } from './ziggs/runtime.js';
19
-
20
- export class ConnectionPool {
21
- /**
22
- * @param {object} opts
23
- * @param {number} [opts.maxActive=50]
24
- * @param {number} [opts.idleTimeoutMs=60000]
25
- */
26
- constructor({ maxActive = 50, idleTimeoutMs = 60_000 } = {}) {
27
- this._mgr = new ConnectionManager({ maxActive, idleTimeoutMs });
28
- // agentId → defineAgent config — kept for ZiggsAgent construction at wake time
29
- this._configs = new Map();
30
- }
31
-
32
- // ---- registration ----
33
-
34
- /**
35
- * Register agents without connecting them. Pool keys use `config.agentId`,
36
- * falling back to `config.workflow?.id` / `config.id`.
37
- *
38
- * @param {Array<object>} configs defineAgent-compatible configs
39
- * @param {Array<object>} [metaArr] Parallel metadata objects keyed by the resolved agentId
40
- */
41
- register(configs, metaArr = []) {
42
- const metaByAgentId = Object.fromEntries(metaArr.map(m => [m.agentId, m]));
43
- for (const config of configs) {
44
- const agentId = config.agentId
45
- ?? config.workflow?.id
46
- ?? config.id;
47
- if (!agentId) {
48
- console.warn('[ConnectionPool] Skipping config with no resolvable agentId (need config.agentId)');
49
- continue;
50
- }
51
- this._configs.set(agentId, config);
52
- this._mgr.register(
53
- agentId,
54
- async () => {
55
- const agent = new ZiggsAgent(this._configs.get(agentId));
56
- await agent.connectAsync();
57
- console.log(`[ConnectionPool] Woke "${agentId}" (active: ${this._mgr.listActive().length + 1}/${this._mgr.maxActive})`);
58
- return agent;
59
- },
60
- (agent) => {
61
- agent.disconnect();
62
- console.log(`[ConnectionPool] Disconnected "${agentId}"`);
63
- },
64
- metaByAgentId[agentId],
65
- );
66
- }
67
- }
68
-
69
- // ---- connection lifecycle ----
70
-
71
- async wake(agentId) { return this._mgr.wake(agentId); }
72
- async sleep(agentId) { return this._mgr.sleep(agentId); }
73
- async disconnectAll() { return this._mgr.sleepAll(); }
74
-
75
- /**
76
- * Wake an agent, deliver a message, and return the agent's response text.
77
- * Awaits the full LLM turn before returning. Resets the idle timer after delivery.
78
- */
79
- async sendTo(agentId, text, metadata = {}, { timeoutMs = 30_000 } = {}) {
80
- const agent = await this.wake(agentId);
81
- const chatId = metadata.chatId;
82
-
83
- const msgBuf = agent.agent?.messageBuffer?._pending;
84
- const bufBefore = msgBuf?.get(chatId)?.length ?? 0;
85
-
86
- const timeoutPromise = new Promise((_, reject) =>
87
- setTimeout(() => reject(new Error(`sendTo timeout for "${agentId}" after ${timeoutMs}ms`)), timeoutMs)
88
- );
89
- await Promise.race([agent.handleMessage(text, metadata), timeoutPromise]);
90
- this._mgr.touch(agentId);
91
-
92
- const pending = msgBuf?.get(chatId);
93
- if (pending && pending.length > bufBefore) {
94
- const newEntries = pending.splice(bufBefore);
95
- if (pending.length === 0) msgBuf.delete(chatId);
96
- return newEntries.map(e => e.text).join('\n\n');
97
- }
98
- return null;
99
- }
100
-
101
- // ---- control socket ----
102
-
103
- /**
104
- * Open the launcher's control socket to the backend. Agents in this pool
105
- * will show as "available" in the store and the backend will push
106
- * `launcher:wake` events for them as needed.
107
- */
108
- startControl({ wsUrl, operatorKey } = {}) {
109
- if (!wsUrl || !operatorKey) {
110
- console.warn('[ConnectionPool] startControl: wsUrl and operatorKey are required — skipping');
111
- return;
112
- }
113
- this._mgr._controlOpts = { wsUrl, operatorKey };
114
- this._mgr.start();
115
- }
116
-
117
- /** Close the control socket. Agents flip to "offline" on the backend immediately. */
118
- stopControl() {
119
- this._mgr._controlHandle?.close();
120
- this._mgr._controlHandle = null;
121
- }
122
-
123
- // ---- querying ----
124
-
125
- query(filter) { return this._mgr.query(filter); }
126
- list() { return this._mgr.list(); }
127
- listActive() { return this._mgr.listActive(); }
128
- get size() { return this._mgr.size; }
129
- get maxActive() { return this._mgr.maxActive; }
130
-
131
- // Legacy compatibility: p1000's list_agents tool reads _meta directly.
132
- get _meta() { return { get: (id) => this._mgr.getMeta(id) }; }
133
- }
@@ -1,73 +0,0 @@
1
- import OpenAI from "openai";
2
-
3
- export class OpenAIAdapter {
4
- constructor({key, model = "gpt-4o-mini"}) {
5
- this.client = new OpenAI({apiKey: key});
6
- this.model = model;
7
- }
8
-
9
- /**
10
- * Single-turn chat. `prompt` is a string; wraps it as a user message.
11
- * Kept for backward compatibility.
12
- */
13
- async chat(prompt, tools = [], temperature = 0.2, systemHints = [], options = {}) {
14
- const messages = [
15
- { role: "system", content: "Respond with valid JSON." },
16
- ...systemHints,
17
- { role: "user", content: prompt }
18
- ];
19
- return this.chatMessages(messages, tools, options);
20
- }
21
-
22
- /**
23
- * Low-level chat that accepts a full messages array.
24
- * Returns { content, tool_calls, message } where message is the raw assistant message.
25
- */
26
- async chatMessages(messages, tools = [], options = {}) {
27
- const openAITools = tools.length > 0
28
- ? tools.map(t => {
29
- const schema = t.schema || t;
30
- const fn = schema.function || schema;
31
- const usageWhen = t.usage?.when ? ` Use when: ${t.usage.when}` : '';
32
- return {
33
- type: 'function',
34
- function: {
35
- ...fn,
36
- description: (fn.description || '') + usageWhen
37
- }
38
- };
39
- })
40
- : undefined;
41
-
42
- try {
43
- const res = await this.client.chat.completions.create({
44
- model: this.model,
45
- messages,
46
- max_completion_tokens: 3000,
47
- response_format: openAITools ? undefined : (options.response_format ?? { type: 'json_object' }),
48
- ...(openAITools && { tools: openAITools }),
49
- ...options,
50
- usage: undefined, // strip internal field if accidentally passed
51
- });
52
-
53
- const msg = res.choices[0].message;
54
- return {
55
- content: msg.content?.trim() ?? null,
56
- tool_calls: msg.tool_calls?.length ? msg.tool_calls : null,
57
- message: msg,
58
- usage: res.usage,
59
- };
60
- } catch (error) {
61
- console.error(`❌ [OpenAIAdapter] LLM API call failed: ${error.message}`, {
62
- model: this.model,
63
- error: error.message,
64
- errorType: error.constructor?.name || 'Error',
65
- status: error.status || error.statusCode || 'unknown',
66
- code: error.code || 'unknown',
67
- ...(error.response && { response: error.response }),
68
- stack: error.stack
69
- });
70
- throw error;
71
- }
72
- }
73
- }