@almadar/integrations 2.41.0 → 2.43.0

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.
@@ -1,6 +1,7 @@
1
- import { g as IntegrationParams, I as IntegrationCallContext, d as IntegrationFactory } from '../BaseIntegration-CWdas-N_.js';
2
- import { o as IntegrationName, m as IntegrationActionName, n as IntegrationContracts, e as CredentialStore } from '../store-DG5OGiuQ.js';
1
+ import { g as IntegrationParams, I as IntegrationCallContext, d as IntegrationFactory } from '../BaseIntegration-Dgwn1ZR6.js';
2
+ import { o as IntegrationName, m as IntegrationActionName, n as IntegrationContracts, e as CredentialStore } from '../store-DP3gfEfJ.js';
3
3
  import '@almadar/core';
4
+ import '@almadar/llm';
4
5
 
5
6
  /**
6
7
  * Signature of the call-service handler returned by `createCallServiceHandler`
@@ -13,6 +13,7 @@ import webpush from 'web-push';
13
13
  import { Readable } from 'stream';
14
14
  import { getAvailableProvider, LLMClient, EmbeddingClient } from '@almadar/llm';
15
15
  import { z } from 'zod';
16
+ import { findInputByAddress, payloadSchemaToJsonSchema, traceActivityPayload, EventPayloadSchema } from '@almadar/core';
16
17
  import { execSync, spawn } from 'child_process';
17
18
  import { tmpdir } from 'os';
18
19
  import * as oidc from 'openid-client';
@@ -409,6 +410,8 @@ var IntegrationFactory = class {
409
410
  constructor() {
410
411
  this.instances = /* @__PURE__ */ new Map();
411
412
  this.configs = /* @__PURE__ */ new Map();
413
+ /** Names whose app-wide instance was registered directly (mock infrastructure). */
414
+ this.registered = /* @__PURE__ */ new Set();
412
415
  }
413
416
  /**
414
417
  * Configure an integration (doesn't instantiate yet). A `principal` scopes
@@ -423,7 +426,8 @@ var IntegrationFactory = class {
423
426
  * back to the app-wide config when no per-principal config exists.
424
427
  */
425
428
  get(name, principal) {
426
- const key = instanceKey(name, principal);
429
+ const registered = this.registered.has(name) && !this.configs.has(instanceKey(name, principal));
430
+ const key = registered ? name : instanceKey(name, principal);
427
431
  const cached = this.instances.get(key);
428
432
  if (cached) {
429
433
  return cached;
@@ -467,6 +471,7 @@ var IntegrationFactory = class {
467
471
  * Register an integration instance directly (used by mock infrastructure)
468
472
  */
469
473
  registerInstance(name, instance, principal) {
474
+ if (principal === void 0) this.registered.add(name);
470
475
  this.instances.set(instanceKey(name, principal), instance);
471
476
  }
472
477
  /**
@@ -477,8 +482,10 @@ var IntegrationFactory = class {
477
482
  invalidate(name) {
478
483
  if (name === void 0) {
479
484
  this.instances.clear();
485
+ this.registered.clear();
480
486
  return;
481
487
  }
488
+ this.registered.delete(name);
482
489
  for (const key of this.instances.keys()) {
483
490
  if (key === name || key.startsWith(`${name}\0`)) {
484
491
  this.instances.delete(key);
@@ -490,6 +497,7 @@ var IntegrationFactory = class {
490
497
  */
491
498
  clear() {
492
499
  this.instances.clear();
500
+ this.registered.clear();
493
501
  }
494
502
  /**
495
503
  * Clear all instances and configs
@@ -497,6 +505,7 @@ var IntegrationFactory = class {
497
505
  reset() {
498
506
  this.instances.clear();
499
507
  this.configs.clear();
508
+ this.registered.clear();
500
509
  }
501
510
  };
502
511
  var installedStore = null;
@@ -1973,6 +1982,148 @@ var EsignIntegration = class extends BaseIntegration {
1973
1982
  }
1974
1983
  };
1975
1984
  registerIntegration("esign", EsignIntegration);
1985
+ var DEFAULT_MAX_STEPS = 8;
1986
+ var TOOL_NAME = /^[a-zA-Z0-9_-]{1,64}$/;
1987
+ function toolName(raw) {
1988
+ if (!TOOL_NAME.test(raw)) {
1989
+ throw new Error(`call-tools: tool name '${raw}' is not a valid tool name (letters, digits, _ and -, at most 64)`);
1990
+ }
1991
+ return raw;
1992
+ }
1993
+ function buildTools(host, tools) {
1994
+ const inputs = host.inputs();
1995
+ const out = /* @__PURE__ */ new Map();
1996
+ for (const tool of tools) {
1997
+ if (tool.event !== void 0) {
1998
+ const input = findInputByAddress(inputs, tool.event, host.caller);
1999
+ if (!input) throw new Error(`call-tools: '${tool.event}' is not a declared external input`);
2000
+ const name = toolName(tool.event.split(".").join("__"));
2001
+ out.set(name, {
2002
+ def: {
2003
+ type: "function",
2004
+ function: { name, description: input.description ?? tool.event, parameters: payloadSchemaToJsonSchema(input.payloadSchema) }
2005
+ },
2006
+ target: { kind: "input", address: tool.event, input }
2007
+ });
2008
+ } else if (tool.read !== void 0) {
2009
+ const name = toolName(`read__${tool.read}`);
2010
+ out.set(name, {
2011
+ def: {
2012
+ type: "function",
2013
+ function: { name, description: `Read the ${tool.read} rows you may see`, parameters: { type: "object", properties: {} } }
2014
+ },
2015
+ target: { kind: "read", entity: tool.read }
2016
+ });
2017
+ }
2018
+ }
2019
+ return out;
2020
+ }
2021
+ function dispatchFailure(response) {
2022
+ if (response.rejections && response.rejections.length > 0) return response.rejections.map((r) => r.code).join(", ");
2023
+ if (response.guardFailed !== void 0) return `guard failed: ${response.guardFailed}`;
2024
+ if (!response.success) return response.error ?? "dispatch failed";
2025
+ const failed = (response.effectResults ?? []).find((r) => !r.success);
2026
+ if (failed) return failed.error ?? `${failed.effect} failed`;
2027
+ if (!response.transitioned) return "no transition";
2028
+ return void 0;
2029
+ }
2030
+ function parseArgs(args) {
2031
+ let value;
2032
+ try {
2033
+ value = JSON.parse(args === "" ? "{}" : args);
2034
+ } catch {
2035
+ value = void 0;
2036
+ }
2037
+ return EventPayloadSchema.safeParse(value);
2038
+ }
2039
+ async function runToolLoop(client, host, request, model, now = Date.now) {
2040
+ const tools = buildTools(host, request.tools);
2041
+ const defs = [...tools.values()].map((t) => t.def);
2042
+ const maxSteps = request.maxSteps ?? DEFAULT_MAX_STEPS;
2043
+ const conversation = [
2044
+ ...request.instructions !== void 0 ? [{ role: "system", content: request.instructions }] : [],
2045
+ ...request.messages.map((m) => ({ role: m.role, content: m.content }))
2046
+ ];
2047
+ const userPrompt = [...request.messages].reverse().find((m) => m.role === "user")?.content ?? "";
2048
+ const steps = [];
2049
+ const record = (activity) => {
2050
+ steps.push(activity);
2051
+ host.message(traceActivityPayload(activity));
2052
+ };
2053
+ const usage = { promptTokens: 0, completionTokens: 0, totalTokens: 0, modelCalls: 0 };
2054
+ while (usage.modelCalls < maxSteps) {
2055
+ const started = now();
2056
+ const turn = await client.callWithTools({ messages: conversation, tools: defs });
2057
+ const ended = now();
2058
+ usage.modelCalls += 1;
2059
+ if (turn.usage) {
2060
+ usage.promptTokens += turn.usage.promptTokens;
2061
+ usage.completionTokens += turn.usage.completionTokens;
2062
+ usage.totalTokens += turn.usage.totalTokens;
2063
+ }
2064
+ conversation.push(turn.message);
2065
+ const calls = turn.message.tool_calls ?? [];
2066
+ record({
2067
+ type: "llm_response",
2068
+ content: turn.message.content ?? "",
2069
+ llm: {
2070
+ service: "llm",
2071
+ provider: model.provider,
2072
+ model: model.model,
2073
+ systemPrompt: request.instructions ?? "",
2074
+ userPrompt,
2075
+ ...calls.length > 0 ? { toolCallsJson: JSON.stringify(calls) } : {},
2076
+ durationMs: ended - started,
2077
+ ...turn.usage ? { promptTokens: turn.usage.promptTokens, completionTokens: turn.usage.completionTokens } : {}
2078
+ },
2079
+ timestamp: ended
2080
+ });
2081
+ if (calls.length === 0) return { reply: turn.message.content ?? "", steps, usage };
2082
+ for (const call of calls) {
2083
+ const args = call.function.arguments;
2084
+ const tool = tools.get(call.function.name);
2085
+ const kind = tool === void 0 ? "invalid" : tool.target.kind;
2086
+ const label = tool === void 0 ? call.function.name : tool.target.kind === "input" ? tool.target.address : tool.target.entity;
2087
+ record({ type: "tool_call", tool: label, kind, argsText: args, timestamp: now() });
2088
+ let content;
2089
+ if (!tool) {
2090
+ content = JSON.stringify({ error: `'${call.function.name}' is not one of your tools` });
2091
+ record({ type: "tool_result", tool: label, kind, success: false, detail: "not an allowed tool", resultText: content, timestamp: now() });
2092
+ } else if (tool.target.kind === "read") {
2093
+ const rows = await host.read(tool.target.entity);
2094
+ content = JSON.stringify({ rows });
2095
+ record({ type: "tool_result", tool: label, kind, success: true, rows: rows.length, resultText: content, timestamp: now() });
2096
+ } else {
2097
+ const parsed = parseArgs(args);
2098
+ if (!parsed.success) {
2099
+ content = JSON.stringify({ error: "arguments must be a JSON object" });
2100
+ record({ type: "tool_result", tool: label, kind, success: false, detail: "arguments are not a JSON object", resultText: content, timestamp: now() });
2101
+ } else {
2102
+ const { input } = tool.target;
2103
+ const response = await host.dispatchInput(input.orbital, {
2104
+ targetTrait: input.trait,
2105
+ event: input.event,
2106
+ payload: parsed.data
2107
+ });
2108
+ const detail = dispatchFailure(response);
2109
+ content = JSON.stringify(response);
2110
+ record({ type: "tool_result", tool: label, kind, success: detail === void 0, ...detail !== void 0 ? { detail } : {}, resultText: content, timestamp: now() });
2111
+ }
2112
+ }
2113
+ conversation.push({ role: "tool", content, tool_call_id: call.id });
2114
+ }
2115
+ }
2116
+ throw new Error(`call-tools: no reply after ${maxSteps} model calls`);
2117
+ }
2118
+
2119
+ // src/integrations/llm/index.ts
2120
+ var CallToolsParamsSchema = z.object({
2121
+ messages: z.array(z.object({ role: z.enum(["user", "assistant"]), content: z.string() })),
2122
+ tools: z.array(z.object({ event: z.string().optional(), read: z.string().optional() })),
2123
+ instructions: z.string().optional(),
2124
+ maxSteps: z.number().int().positive().optional(),
2125
+ model: z.string().optional()
2126
+ });
1976
2127
  var LLMIntegration = class extends BaseIntegration {
1977
2128
  constructor(config) {
1978
2129
  super(config);
@@ -2013,7 +2164,7 @@ var LLMIntegration = class extends BaseIntegration {
2013
2164
  }
2014
2165
  return this.embeddings;
2015
2166
  }
2016
- async execute(action, params) {
2167
+ async execute(action, params, context) {
2017
2168
  const validation = this.validateParams(action, params);
2018
2169
  if (!validation.valid) return this.validationFailure(action, validation.errors);
2019
2170
  const startTime = Date.now();
@@ -2035,6 +2186,10 @@ var LLMIntegration = class extends BaseIntegration {
2035
2186
  case "embed":
2036
2187
  data = await this.executeWithRetry(() => this.embed(params));
2037
2188
  break;
2189
+ // Never retried: a retry would re-fire inputs the first attempt already fired.
2190
+ case "call-tools":
2191
+ data = await this.callTools(params, context);
2192
+ break;
2038
2193
  default:
2039
2194
  throw new UnknownActionError(this.config.name, action);
2040
2195
  }
@@ -2125,6 +2280,14 @@ Return ONLY valid JSON matching the schema.`,
2125
2280
  });
2126
2281
  return result;
2127
2282
  }
2283
+ async callTools(params, context) {
2284
+ if (!context?.host) {
2285
+ throw new Error("llm.call-tools needs host ports: run it from a host that lends the running app (call-service on a server)");
2286
+ }
2287
+ const request = CallToolsParamsSchema.parse(params);
2288
+ const client = request.model !== void 0 ? new LLMClient({ provider: this.provider, model: request.model }) : this.getClient();
2289
+ return runToolLoop(client, context.host, request, { provider: client.getProvider(), model: client.getModelName() });
2290
+ }
2128
2291
  async embed(params) {
2129
2292
  const { texts } = params;
2130
2293
  if (!Array.isArray(texts)) {