@fonoster/autopilot 0.30.0 → 0.31.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.
- package/README.md +5 -5
- package/dist/knowledge/MeteredEmbeddings.d.ts +47 -0
- package/dist/knowledge/MeteredEmbeddings.js +58 -0
- package/dist/models/createLanguageModel.d.ts +2 -2
- package/dist/models/createLanguageModel.js +2 -2
- package/dist/models/evaluations/createEvalEffectiveConfig.d.ts +6 -6
- package/dist/models/evaluations/createEvalEffectiveConfig.js +7 -7
- package/dist/models/evaluations/createEvaluateIntelligence.d.ts +15 -4
- package/dist/models/evaluations/createEvaluateIntelligence.js +33 -12
- package/dist/models/evaluations/createTestTextSimilarity.d.ts +7 -1
- package/dist/models/evaluations/createTestTextSimilarity.js +19 -1
- package/dist/models/evaluations/evalTestCases.d.ts +2 -2
- package/dist/models/evaluations/evalTestCases.js +7 -7
- package/dist/models/evaluations/evaluateScenario.js +2 -2
- package/dist/models/evaluations/evaluateStep.d.ts +1 -1
- package/dist/models/evaluations/evaluateStep.js +2 -2
- package/dist/models/evaluations/extractAiResponse.d.ts +2 -2
- package/dist/models/evaluations/extractAiResponse.js +3 -3
- package/dist/models/evaluations/runEval.d.ts +2 -2
- package/dist/models/evaluations/runEval.js +7 -7
- package/dist/models/evaluations/session/EvalSession.d.ts +1 -1
- package/dist/models/evaluations/session/EvalSession.js +1 -1
- package/dist/models/evaluations/session/evaluateSessionScenario.d.ts +3 -3
- package/dist/models/evaluations/session/evaluateSessionScenario.js +4 -4
- package/dist/models/evaluations/session/runSessionEval.d.ts +7 -4
- package/dist/models/evaluations/session/runSessionEval.js +7 -7
- package/dist/models/evaluations/textSimilaryPrompt.d.ts +1 -1
- package/dist/models/evaluations/textSimilaryPrompt.js +1 -1
- package/dist/models/evaluations/types.d.ts +5 -5
- package/dist/models/types.d.ts +3 -3
- package/dist/session/Agent.d.ts +38 -3
- package/dist/session/Agent.js +90 -16
- package/dist/session/config.d.ts +2 -2
- package/dist/session/config.js +4 -4
- package/dist/session/createAutopilot.d.ts +12 -11
- package/dist/session/createAutopilot.js +48 -16
- package/dist/session/hosted.d.ts +23 -25
- package/dist/session/hosted.js +43 -48
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/knowledge.d.ts +1 -1
- package/dist/session/llm.d.ts +28 -1
- package/dist/session/llm.js +50 -6
- package/dist/session/main.js +2 -1
- package/dist/session/meteredThink.d.ts +45 -0
- package/dist/session/meteredThink.js +125 -0
- package/dist/session/telemetry.d.ts +34 -0
- package/dist/session/telemetry.js +55 -0
- package/dist/session/tools.d.ts +1 -1
- package/dist/session/tools.js +1 -1
- package/package.json +9 -9
- package/dist/session/convertLegacyAssistant.d.ts +0 -77
- package/dist/session/convertLegacyAssistant.js +0 -118
package/dist/session/llm.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RegistryLlmResolver = exports.LlmResolutionError = exports.OPENAI_COMPATIBLE = exports.LayeredCredentials = exports.ConfigCredentials = void 0;
|
|
3
|
+
exports.RegistryLlmResolver = exports.LlmResolutionError = exports.OPENAI_COMPATIBLE = exports.LayeredCredentials = exports.KeyUnavailableError = exports.ConfigCredentials = void 0;
|
|
4
|
+
exports.resolveCredential = resolveCredential;
|
|
4
5
|
const community_1 = require("@fonoster/community");
|
|
5
6
|
const openai_1 = require("@fonoster/community/openai");
|
|
6
7
|
Object.defineProperty(exports, "OPENAI_COMPATIBLE", { enumerable: true, get: function () { return openai_1.OPENAI_COMPATIBLE; } });
|
|
@@ -18,21 +19,58 @@ class ConfigCredentials {
|
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
exports.ConfigCredentials = ConfigCredentials;
|
|
22
|
+
/**
|
|
23
|
+
* A workspace's own key could not be read (its store failed). It never falls back to the platform
|
|
24
|
+
* key (remove-platform-key-fallback): the session ends with reason key_unavailable.
|
|
25
|
+
*/
|
|
26
|
+
class KeyUnavailableError extends Error {
|
|
27
|
+
constructor(ref, cause) {
|
|
28
|
+
super(`the workspace's key ${ref} could not be read: ${cause?.message ?? cause}`);
|
|
29
|
+
this.ref = ref;
|
|
30
|
+
this.reason = "key_unavailable";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.KeyUnavailableError = KeyUnavailableError;
|
|
21
34
|
/** Workspace Secrets (BYOK) in front of platform credentials. */
|
|
22
35
|
class LayeredCredentials {
|
|
23
36
|
constructor(sources) {
|
|
24
37
|
this.sources = sources;
|
|
25
38
|
}
|
|
26
39
|
async get(ref, scope) {
|
|
40
|
+
return (await this.resolve(ref, scope))?.key;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The first key found and whose it is (add-usage-metering): the operator's config holds the
|
|
44
|
+
* platform keys; every other source holds the workspace's own.
|
|
45
|
+
*/
|
|
46
|
+
async resolve(ref, scope) {
|
|
27
47
|
for (const s of this.sources) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
48
|
+
let key;
|
|
49
|
+
try {
|
|
50
|
+
key = await s.get(ref, scope);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (s instanceof ConfigCredentials)
|
|
54
|
+
throw e;
|
|
55
|
+
throw new KeyUnavailableError(ref, e);
|
|
56
|
+
}
|
|
57
|
+
if (key)
|
|
58
|
+
return {
|
|
59
|
+
key,
|
|
60
|
+
source: s instanceof ConfigCredentials ? "platform" : "workspace"
|
|
61
|
+
};
|
|
31
62
|
}
|
|
32
63
|
return undefined;
|
|
33
64
|
}
|
|
34
65
|
}
|
|
35
66
|
exports.LayeredCredentials = LayeredCredentials;
|
|
67
|
+
/** A credential and its source; a source that cannot tell is the platform's. */
|
|
68
|
+
async function resolveCredential(credentials, ref, scope) {
|
|
69
|
+
if (credentials instanceof LayeredCredentials)
|
|
70
|
+
return credentials.resolve(ref, scope);
|
|
71
|
+
const key = await credentials.get(ref, scope);
|
|
72
|
+
return key ? { key, source: "platform" } : undefined;
|
|
73
|
+
}
|
|
36
74
|
class LlmResolutionError extends Error {
|
|
37
75
|
}
|
|
38
76
|
exports.LlmResolutionError = LlmResolutionError;
|
|
@@ -59,14 +97,20 @@ class RegistryLlmResolver {
|
|
|
59
97
|
throw new LlmResolutionError(`adapter ${provider.adapter} has no language model implementation in Autopilot yet`);
|
|
60
98
|
const baseUrl = this.options.endpoints?.[provider.id] ??
|
|
61
99
|
this.options.endpoints?.[provider.adapter] ??
|
|
100
|
+
provider.endpoints?.llm ??
|
|
62
101
|
llm.defaultEndpoint;
|
|
63
102
|
if (!baseUrl)
|
|
64
103
|
throw new LlmResolutionError(`no endpoint for provider ${provider.id} (adapter ${provider.adapter})`);
|
|
65
|
-
|
|
66
|
-
|
|
104
|
+
// A provider without a credentialRef is keyless (local models, the fakes).
|
|
105
|
+
const credential = provider.credentialRef
|
|
106
|
+
? await resolveCredential(this.options.credentials, provider.credentialRef, scope)
|
|
107
|
+
: undefined;
|
|
108
|
+
const apiKey = credential?.key;
|
|
109
|
+
if (provider.credentialRef && !apiKey)
|
|
67
110
|
throw new LlmResolutionError(`no credential ${provider.credentialRef} for provider ${provider.id}`);
|
|
68
111
|
return {
|
|
69
112
|
id,
|
|
113
|
+
keySource: credential?.source ?? "platform",
|
|
70
114
|
think: llm.create({ baseUrl, apiKey }, id.slice(providerId.length + 1), settings),
|
|
71
115
|
...(resolution.event
|
|
72
116
|
? {
|
package/dist/session/main.js
CHANGED
|
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
24
24
|
const common_1 = require("@fonoster/common");
|
|
25
25
|
const logger_1 = require("@fonoster/logger");
|
|
26
26
|
const hosted_1 = require("./hosted");
|
|
27
|
+
const telemetry_1 = require("./telemetry");
|
|
27
28
|
const logger = (0, logger_1.getLogger)({ service: "autopilot", filePath: __filename });
|
|
28
29
|
(0, common_1.logUnhandledRejections)(logger);
|
|
29
30
|
// --config, AUTOPILOT_SESSION_CONFIG, else /config/autopilot.yaml (or an older location).
|
|
@@ -37,7 +38,7 @@ if (!file) {
|
|
|
37
38
|
}
|
|
38
39
|
const config = (0, hosted_1.loadHostedConfig)(file);
|
|
39
40
|
// Off unless an OTLP endpoint is configured (telemetry.otlpEndpoint or OTEL_EXPORTER_OTLP_*).
|
|
40
|
-
(0,
|
|
41
|
+
(0, telemetry_1.startTelemetry)(config.telemetry);
|
|
41
42
|
(0, hosted_1.startHostedAutopilot)(config).catch((e) => {
|
|
42
43
|
logger.error(`hosted autopilot failed to start: ${e.message}`);
|
|
43
44
|
process.exit(1);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Usage } from "@fonoster/common";
|
|
20
|
+
import { Think, TokenUsage } from "@fonoster/community";
|
|
21
|
+
/** Where a model's usage goes: Autopilot sends it to the call (usage.report). */
|
|
22
|
+
export type UsageSink = {
|
|
23
|
+
usage(lines: Usage.UsageLine[]): void;
|
|
24
|
+
/** A completed request whose vendor reported no usage (add-usage-metering). */
|
|
25
|
+
missing(catalogId: string): void;
|
|
26
|
+
};
|
|
27
|
+
export type MeteredModel = {
|
|
28
|
+
/** The catalog id (`<provider>/<model>`). */
|
|
29
|
+
catalogId: string;
|
|
30
|
+
keySource: Usage.KeySource;
|
|
31
|
+
};
|
|
32
|
+
/** How long an interrupted reply is read in the background for its usage report (design D6). */
|
|
33
|
+
export declare const DRAIN_DEADLINE_MS = 30000;
|
|
34
|
+
export declare function usageLines(model: MeteredModel, usage: TokenUsage): Usage.UsageLine[];
|
|
35
|
+
/**
|
|
36
|
+
* A Think that meters its model (add-usage-metering D6). Chunks pass through; the vendor's usage
|
|
37
|
+
* chunk is reported, never forwarded. When the caller stops reading (barge-in, a newer turn), the
|
|
38
|
+
* stream ends for the caller at once but keeps being read in the background, so the vendor's usage
|
|
39
|
+
* for the whole request still arrives; after the deadline, or when `closed` aborts (the session
|
|
40
|
+
* ended), the request is reported as missing usage. A request the vendor failed reports nothing.
|
|
41
|
+
*/
|
|
42
|
+
export declare function meteredThink(inner: Think, model: MeteredModel, sink: UsageSink, options?: {
|
|
43
|
+
drainMs?: number;
|
|
44
|
+
closed?: AbortSignal;
|
|
45
|
+
}): Think;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DRAIN_DEADLINE_MS = void 0;
|
|
4
|
+
exports.usageLines = usageLines;
|
|
5
|
+
exports.meteredThink = meteredThink;
|
|
6
|
+
/** How long an interrupted reply is read in the background for its usage report (design D6). */
|
|
7
|
+
exports.DRAIN_DEADLINE_MS = 30000;
|
|
8
|
+
const abortError = () => {
|
|
9
|
+
const e = new Error("aborted");
|
|
10
|
+
e.name = "AbortError";
|
|
11
|
+
return e;
|
|
12
|
+
};
|
|
13
|
+
/** Resolves like `p`, or rejects with an AbortError as soon as `signal` aborts. */
|
|
14
|
+
function raceAbort(p, signal) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const onAbort = () => reject(abortError());
|
|
17
|
+
if (signal.aborted)
|
|
18
|
+
return onAbort();
|
|
19
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
20
|
+
p.then((v) => {
|
|
21
|
+
signal.removeEventListener("abort", onAbort);
|
|
22
|
+
resolve(v);
|
|
23
|
+
}, (e) => {
|
|
24
|
+
signal.removeEventListener("abort", onAbort);
|
|
25
|
+
reject(e);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function usageLines(model, usage) {
|
|
30
|
+
const key = {
|
|
31
|
+
provider: model.catalogId.split("/")[0],
|
|
32
|
+
model: model.catalogId,
|
|
33
|
+
keySource: model.keySource
|
|
34
|
+
};
|
|
35
|
+
const lines = [
|
|
36
|
+
{ meter: "llm.tokens.in", quantity: usage.inputTokens, ...key },
|
|
37
|
+
{ meter: "llm.tokens.out", quantity: usage.outputTokens, ...key }
|
|
38
|
+
];
|
|
39
|
+
if (usage.cachedTokens > 0)
|
|
40
|
+
lines.push({
|
|
41
|
+
meter: "llm.tokens.cached",
|
|
42
|
+
quantity: usage.cachedTokens,
|
|
43
|
+
...key
|
|
44
|
+
});
|
|
45
|
+
return lines;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A Think that meters its model (add-usage-metering D6). Chunks pass through; the vendor's usage
|
|
49
|
+
* chunk is reported, never forwarded. When the caller stops reading (barge-in, a newer turn), the
|
|
50
|
+
* stream ends for the caller at once but keeps being read in the background, so the vendor's usage
|
|
51
|
+
* for the whole request still arrives; after the deadline, or when `closed` aborts (the session
|
|
52
|
+
* ended), the request is reported as missing usage. A request the vendor failed reports nothing.
|
|
53
|
+
*/
|
|
54
|
+
function meteredThink(inner, model, sink, options = {}) {
|
|
55
|
+
const settle = (usage) => usage
|
|
56
|
+
? sink.usage(usageLines(model, usage))
|
|
57
|
+
: sink.missing(model.catalogId);
|
|
58
|
+
return {
|
|
59
|
+
async *stream(messages, tools, signal) {
|
|
60
|
+
const request = new AbortController();
|
|
61
|
+
const stopRequest = () => request.abort();
|
|
62
|
+
options.closed?.addEventListener("abort", stopRequest, { once: true });
|
|
63
|
+
const it = inner
|
|
64
|
+
.stream(messages, tools, request.signal)[Symbol.asyncIterator]();
|
|
65
|
+
let usage;
|
|
66
|
+
let pending;
|
|
67
|
+
let state = "reading";
|
|
68
|
+
const done = () => options.closed?.removeEventListener("abort", stopRequest);
|
|
69
|
+
const drain = async () => {
|
|
70
|
+
const deadline = setTimeout(stopRequest, options.drainMs ?? exports.DRAIN_DEADLINE_MS);
|
|
71
|
+
try {
|
|
72
|
+
for (;;) {
|
|
73
|
+
const r = await (pending ?? it.next());
|
|
74
|
+
pending = undefined;
|
|
75
|
+
if (r.done)
|
|
76
|
+
break;
|
|
77
|
+
if (r.value.type === "usage")
|
|
78
|
+
usage = r.value.usage;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Cut by the deadline or the session's end: whatever arrived is all there is.
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
clearTimeout(deadline);
|
|
86
|
+
done();
|
|
87
|
+
}
|
|
88
|
+
settle(usage);
|
|
89
|
+
};
|
|
90
|
+
try {
|
|
91
|
+
for (;;) {
|
|
92
|
+
pending = it.next();
|
|
93
|
+
pending.catch(() => undefined);
|
|
94
|
+
const r = await raceAbort(pending, signal);
|
|
95
|
+
pending = undefined;
|
|
96
|
+
if (r.done) {
|
|
97
|
+
state = "done";
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
if (r.value.type === "usage")
|
|
101
|
+
usage = r.value.usage;
|
|
102
|
+
else
|
|
103
|
+
yield r.value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
if (!signal.aborted)
|
|
108
|
+
state = "failed";
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
if (state === "done") {
|
|
113
|
+
done();
|
|
114
|
+
settle(usage);
|
|
115
|
+
}
|
|
116
|
+
else if (state === "failed") {
|
|
117
|
+
done();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
void drain();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
import { Telemetry, TelemetryConfig } from "@fonoster/common";
|
|
20
|
+
/**
|
|
21
|
+
* Autopilot's telemetry: a stable handle (like AppBridge's) that delegates to what
|
|
22
|
+
* `startTelemetry`/`setTelemetry` installed. OTLP when an endpoint is configured
|
|
23
|
+
* (telemetry.otlpEndpoint or OTEL_EXPORTER_OTLP_*), a no-op otherwise.
|
|
24
|
+
*
|
|
25
|
+
* Spans: autopilot.session (child of AppBridge's call span, via session.start `traceparent`),
|
|
26
|
+
* autopilot.turn (STT final to the end of the reply) and autopilot.llm.request. Metrics:
|
|
27
|
+
* fonoster.autopilot.turns, fonoster.autopilot.llm.requests and fonoster.autopilot.llm.latency_ms
|
|
28
|
+
* (the sum of the time to the first token; divide by requests for the mean).
|
|
29
|
+
*/
|
|
30
|
+
export declare const telemetry: Telemetry;
|
|
31
|
+
/** Installs a telemetry implementation (tests). */
|
|
32
|
+
export declare function setTelemetry(t: Telemetry): void;
|
|
33
|
+
/** Starts telemetry with the config's endpoint, once the config is loaded. */
|
|
34
|
+
export declare function startTelemetry(config: TelemetryConfig): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.telemetry = void 0;
|
|
4
|
+
exports.setTelemetry = setTelemetry;
|
|
5
|
+
exports.startTelemetry = startTelemetry;
|
|
6
|
+
/**
|
|
7
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
8
|
+
* http://github.com/fonoster/fonoster
|
|
9
|
+
*
|
|
10
|
+
* This file is part of Fonoster
|
|
11
|
+
*
|
|
12
|
+
* Licensed under the MIT License (the "License");
|
|
13
|
+
* you may not use this file except in compliance with
|
|
14
|
+
* the License. You may obtain a copy of the License at
|
|
15
|
+
*
|
|
16
|
+
* https://opensource.org/licenses/MIT
|
|
17
|
+
*
|
|
18
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
19
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
20
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
21
|
+
* See the License for the specific language governing permissions and
|
|
22
|
+
* limitations under the License.
|
|
23
|
+
*/
|
|
24
|
+
const common_1 = require("@fonoster/common");
|
|
25
|
+
let impl = (0, common_1.initTelemetry)("fonoster-autopilot");
|
|
26
|
+
/**
|
|
27
|
+
* Autopilot's telemetry: a stable handle (like AppBridge's) that delegates to what
|
|
28
|
+
* `startTelemetry`/`setTelemetry` installed. OTLP when an endpoint is configured
|
|
29
|
+
* (telemetry.otlpEndpoint or OTEL_EXPORTER_OTLP_*), a no-op otherwise.
|
|
30
|
+
*
|
|
31
|
+
* Spans: autopilot.session (child of AppBridge's call span, via session.start `traceparent`),
|
|
32
|
+
* autopilot.turn (STT final to the end of the reply) and autopilot.llm.request. Metrics:
|
|
33
|
+
* fonoster.autopilot.turns, fonoster.autopilot.llm.requests and fonoster.autopilot.llm.latency_ms
|
|
34
|
+
* (the sum of the time to the first token; divide by requests for the mean).
|
|
35
|
+
*/
|
|
36
|
+
exports.telemetry = {
|
|
37
|
+
get enabled() {
|
|
38
|
+
return impl.enabled;
|
|
39
|
+
},
|
|
40
|
+
startCall: (...a) => impl.startCall(...a),
|
|
41
|
+
startSpan: (...a) => impl.startSpan(...a),
|
|
42
|
+
traceparent: (...a) => impl.traceparent(...a),
|
|
43
|
+
log: (...a) => impl.log(...a),
|
|
44
|
+
count: (...a) => impl.count(...a),
|
|
45
|
+
gauge: (...a) => impl.gauge(...a),
|
|
46
|
+
shutdown: () => impl.shutdown()
|
|
47
|
+
};
|
|
48
|
+
/** Installs a telemetry implementation (tests). */
|
|
49
|
+
function setTelemetry(t) {
|
|
50
|
+
impl = t;
|
|
51
|
+
}
|
|
52
|
+
/** Starts telemetry with the config's endpoint, once the config is loaded. */
|
|
53
|
+
function startTelemetry(config) {
|
|
54
|
+
impl = (0, common_1.initTelemetry)("fonoster-autopilot", undefined, (0, common_1.telemetryEnv)(config));
|
|
55
|
+
}
|
package/dist/session/tools.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface ToolPort {
|
|
|
10
10
|
run(name: string, args: Record<string, unknown>, signal: AbortSignal): Promise<ToolOutcome>;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* HTTP tools from the
|
|
13
|
+
* HTTP tools from the agent config (`operation.method`, `url`, `headers`). Tool URLs are
|
|
14
14
|
* tenant input: they go through safeFetch (public destinations only, redirects re-checked,
|
|
15
15
|
* 10 s timeout; RV-B2).
|
|
16
16
|
*/
|
package/dist/session/tools.js
CHANGED
|
@@ -22,7 +22,7 @@ exports.HttpToolPort = void 0;
|
|
|
22
22
|
const url_1 = require("url");
|
|
23
23
|
const common_1 = require("@fonoster/common");
|
|
24
24
|
/**
|
|
25
|
-
* HTTP tools from the
|
|
25
|
+
* HTTP tools from the agent config (`operation.method`, `url`, `headers`). Tool URLs are
|
|
26
26
|
* tenant input: they go through safeFetch (public destinations only, redirects re-checked,
|
|
27
27
|
* 10 s timeout; RV-B2).
|
|
28
28
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonoster/autopilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Voice AI for the Fonoster platform",
|
|
5
5
|
"author": "Pedro Sanders <psanders@fonoster.com>",
|
|
6
6
|
"homepage": "https://github.com/fonoster/fonoster#readme",
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"prebuild": "rimraf ./dist tsconfig.tsbuildinfo",
|
|
16
16
|
"build": "tsc -b tsconfig.json",
|
|
17
|
-
"
|
|
18
|
-
"generate:readme": "node ../../.scripts/gen-readme.cjs",
|
|
17
|
+
"generate:readme": "fonoster-readme",
|
|
19
18
|
"start:hosted": "node dist/session/main.js"
|
|
20
19
|
},
|
|
21
20
|
"files": [
|
|
@@ -34,11 +33,11 @@
|
|
|
34
33
|
"dependencies": {
|
|
35
34
|
"@aws-sdk/client-s3": "^3.958.0",
|
|
36
35
|
"@dmitryrechkin/json-schema-to-zod": "^1.0.1",
|
|
37
|
-
"@fonoster/common": "^0.
|
|
38
|
-
"@fonoster/community": "^0.
|
|
39
|
-
"@fonoster/logger": "^0.
|
|
40
|
-
"@fonoster/types": "^0.
|
|
41
|
-
"@fonoster/voice": "^0.
|
|
36
|
+
"@fonoster/common": "^0.31.0",
|
|
37
|
+
"@fonoster/community": "^0.31.0",
|
|
38
|
+
"@fonoster/logger": "^0.31.0",
|
|
39
|
+
"@fonoster/types": "^0.31.0",
|
|
40
|
+
"@fonoster/voice": "^0.31.0",
|
|
42
41
|
"@grpc/grpc-js": "~1.14.6",
|
|
43
42
|
"@langchain/anthropic": "^1.3.3",
|
|
44
43
|
"@langchain/community": "^1.1.1",
|
|
@@ -52,10 +51,11 @@
|
|
|
52
51
|
"js-yaml": "^4.1.1",
|
|
53
52
|
"langchain": "^1.2.3",
|
|
54
53
|
"pdf-parse": "^1.1.1",
|
|
54
|
+
"undici": "^7.30.1",
|
|
55
55
|
"xstate": "^5.17.3",
|
|
56
56
|
"zod": "^3.25.76"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "f0a5102dddeea0f70f659234108805bc10690f2e",
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"yaml": "^2.9.0"
|
|
61
61
|
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
-
* http://github.com/fonoster/fonoster
|
|
4
|
-
*
|
|
5
|
-
* This file is part of Fonoster
|
|
6
|
-
*
|
|
7
|
-
* Licensed under the MIT License (the "License");
|
|
8
|
-
* you may not use this file except in compliance with
|
|
9
|
-
* the License. You may obtain a copy of the License at
|
|
10
|
-
*
|
|
11
|
-
* https://opensource.org/licenses/MIT
|
|
12
|
-
*
|
|
13
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
-
* See the License for the specific language governing permissions and
|
|
17
|
-
* limitations under the License.
|
|
18
|
-
*/
|
|
19
|
-
import { SessionAssistant } from "./config";
|
|
20
|
-
type Legacy = {
|
|
21
|
-
conversationSettings?: Record<string, unknown> & {
|
|
22
|
-
maxSpeechWaitTimeout?: number;
|
|
23
|
-
vad?: unknown;
|
|
24
|
-
transferOptions?: {
|
|
25
|
-
phoneNumber: string;
|
|
26
|
-
message: string;
|
|
27
|
-
timeout?: number;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
languageModel?: Record<string, unknown> & {
|
|
31
|
-
provider?: string;
|
|
32
|
-
model?: string;
|
|
33
|
-
apiKey?: string;
|
|
34
|
-
baseUrl?: string;
|
|
35
|
-
};
|
|
36
|
-
eventsHook?: {
|
|
37
|
-
url: string;
|
|
38
|
-
events?: string[];
|
|
39
|
-
headers?: Record<string, string>;
|
|
40
|
-
};
|
|
41
|
-
audioFilters?: unknown;
|
|
42
|
-
testCases?: unknown;
|
|
43
|
-
};
|
|
44
|
-
/**
|
|
45
|
-
* VAD-era settings → the patience knob (add-turn-taking 3.2, AP-B3). maxSpeechWaitTimeout was
|
|
46
|
-
* extra waiting after the caller's speech before answering; 0 (the old default) is today's
|
|
47
|
-
* behaviour, normal. Any extra wait becomes high (about 1.6 × the gateway's silence timeout, and
|
|
48
|
-
* more as the gateway adapts to a caller who pauses). Nothing maps to low: the old settings had
|
|
49
|
-
* no way to answer faster than the default. allowUserBargeIn keeps its meaning: it sets
|
|
50
|
-
* `interruptible` on every speak and whether Autopilot sends clear on turn.user_started.
|
|
51
|
-
*/
|
|
52
|
-
export declare function patienceFor(maxSpeechWaitTimeout?: number): "low" | "normal" | "high";
|
|
53
|
-
export type AssistantConversion = {
|
|
54
|
-
assistant: SessionAssistant;
|
|
55
|
-
/**
|
|
56
|
-
* An inline key to store as the workspace's key for `providerId` (the config no longer holds
|
|
57
|
-
* it). The migration job binds it like the dashboard's Vendor keys page does (a Secret named
|
|
58
|
-
* `provider-key/<provider>` in workspace_provider_keys), which is where the registry's
|
|
59
|
-
* credentialRef lookup finds workspace keys (AP-B4, LG-B1).
|
|
60
|
-
*/
|
|
61
|
-
secret?: {
|
|
62
|
-
providerId: string;
|
|
63
|
-
name: string;
|
|
64
|
-
value: string;
|
|
65
|
-
};
|
|
66
|
-
/** The retired eventsHook URL, for creating a workspace webhook endpoint. */
|
|
67
|
-
webhook?: {
|
|
68
|
-
url: string;
|
|
69
|
-
};
|
|
70
|
-
/** Audio filters that now belong in the app's configuration. */
|
|
71
|
-
appFilters?: unknown;
|
|
72
|
-
notices: string[];
|
|
73
|
-
};
|
|
74
|
-
export declare function convertLegacyAssistant(old: Legacy, options: {
|
|
75
|
-
assistantRef: string;
|
|
76
|
-
}): AssistantConversion;
|
|
77
|
-
export {};
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.patienceFor = patienceFor;
|
|
4
|
-
exports.convertLegacyAssistant = convertLegacyAssistant;
|
|
5
|
-
/**
|
|
6
|
-
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
7
|
-
* http://github.com/fonoster/fonoster
|
|
8
|
-
*
|
|
9
|
-
* This file is part of Fonoster
|
|
10
|
-
*
|
|
11
|
-
* Licensed under the MIT License (the "License");
|
|
12
|
-
* you may not use this file except in compliance with
|
|
13
|
-
* the License. You may obtain a copy of the License at
|
|
14
|
-
*
|
|
15
|
-
* https://opensource.org/licenses/MIT
|
|
16
|
-
*
|
|
17
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
18
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
19
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
20
|
-
* See the License for the specific language governing permissions and
|
|
21
|
-
* limitations under the License.
|
|
22
|
-
*/
|
|
23
|
-
const config_1 = require("./config");
|
|
24
|
-
// The one-time conversion of stored assistants at cutover (owner decision, rework-autopilot 5.3).
|
|
25
|
-
// It never writes anything itself: it returns the new config plus what the migration job must do
|
|
26
|
-
// around it (create a workspace Secret, a webhook endpoint, app audio filters) and notices for the
|
|
27
|
-
// workspace.
|
|
28
|
-
const PROVIDERS = ["openai", "groq", "ollama", "google", "anthropic"];
|
|
29
|
-
const PLACEHOLDER_KEYS = new Set(["", "your-api-key", "changeme", "xxx"]);
|
|
30
|
-
/**
|
|
31
|
-
* VAD-era settings → the patience knob (add-turn-taking 3.2, AP-B3). maxSpeechWaitTimeout was
|
|
32
|
-
* extra waiting after the caller's speech before answering; 0 (the old default) is today's
|
|
33
|
-
* behaviour, normal. Any extra wait becomes high (about 1.6 × the gateway's silence timeout, and
|
|
34
|
-
* more as the gateway adapts to a caller who pauses). Nothing maps to low: the old settings had
|
|
35
|
-
* no way to answer faster than the default. allowUserBargeIn keeps its meaning: it sets
|
|
36
|
-
* `interruptible` on every speak and whether Autopilot sends clear on turn.user_started.
|
|
37
|
-
*/
|
|
38
|
-
function patienceFor(maxSpeechWaitTimeout) {
|
|
39
|
-
return (maxSpeechWaitTimeout ?? 0) > 0 ? "high" : "normal";
|
|
40
|
-
}
|
|
41
|
-
const DEFAULT_VAD = {
|
|
42
|
-
activationThreshold: 0.4,
|
|
43
|
-
deactivationThreshold: 0.25,
|
|
44
|
-
debounceFrames: 4
|
|
45
|
-
};
|
|
46
|
-
function isDefaultVad(vad) {
|
|
47
|
-
if (!vad || typeof vad !== "object")
|
|
48
|
-
return true;
|
|
49
|
-
const v = vad;
|
|
50
|
-
return Object.keys(DEFAULT_VAD).every((k) => v[k] === undefined || v[k] === DEFAULT_VAD[k]);
|
|
51
|
-
}
|
|
52
|
-
function convertLegacyAssistant(old, options) {
|
|
53
|
-
const notices = [];
|
|
54
|
-
const lm = { ...(old.languageModel ?? {}) };
|
|
55
|
-
const cs = { ...(old.conversationSettings ?? {}) };
|
|
56
|
-
const result = { notices };
|
|
57
|
-
const provider = String(lm.provider ?? "");
|
|
58
|
-
if (!PROVIDERS.includes(provider))
|
|
59
|
-
throw new Error(`unknown language model provider "${provider}"`);
|
|
60
|
-
const model = `${provider}/${lm.model}`;
|
|
61
|
-
if (typeof lm.apiKey === "string" &&
|
|
62
|
-
!PLACEHOLDER_KEYS.has(lm.apiKey.trim())) {
|
|
63
|
-
void options.assistantRef;
|
|
64
|
-
result.secret = {
|
|
65
|
-
providerId: provider,
|
|
66
|
-
name: `provider-key/${provider}`,
|
|
67
|
-
value: lm.apiKey
|
|
68
|
-
};
|
|
69
|
-
notices.push(`The ${provider} key moved out of the assistant into the workspace's ${provider} vendor key (Secret "${result.secret.name}").`);
|
|
70
|
-
}
|
|
71
|
-
if (lm.baseUrl) {
|
|
72
|
-
notices.push(`The custom model endpoint ${lm.baseUrl} was removed; endpoints now come from the provider registry.`);
|
|
73
|
-
}
|
|
74
|
-
const patience = patienceFor(cs.maxSpeechWaitTimeout);
|
|
75
|
-
if (patience !== "normal") {
|
|
76
|
-
notices.push(`maxSpeechWaitTimeout (${cs.maxSpeechWaitTimeout} ms) became patience "${patience}"; turn timing now comes from the gateway.`);
|
|
77
|
-
}
|
|
78
|
-
if (cs.vad !== undefined && !isDefaultVad(cs.vad))
|
|
79
|
-
notices.push("Custom VAD settings were removed; turn detection now runs in the gateway. For noisy lines use the app's audio filters or a more patient setting.");
|
|
80
|
-
if (cs.transferOptions?.timeout !== undefined &&
|
|
81
|
-
cs.transferOptions.timeout !== 30000) {
|
|
82
|
-
notices.push(`The transfer timeout (${cs.transferOptions.timeout} ms) is no longer configurable per assistant.`);
|
|
83
|
-
}
|
|
84
|
-
if (old.eventsHook?.url) {
|
|
85
|
-
result.webhook = { url: old.eventsHook.url };
|
|
86
|
-
notices.push(`Assistant webhooks are retired; create a workspace webhook for ${old.eventsHook.url} (call.started, call.ended, call_record.sealed).`);
|
|
87
|
-
}
|
|
88
|
-
if (old.audioFilters !== undefined) {
|
|
89
|
-
result.appFilters = old.audioFilters;
|
|
90
|
-
notices.push("Audio filters moved to the app's configuration.");
|
|
91
|
-
}
|
|
92
|
-
const { maxSpeechWaitTimeout, vad, transferOptions, ...settings } = cs;
|
|
93
|
-
void vad;
|
|
94
|
-
void maxSpeechWaitTimeout;
|
|
95
|
-
const assistant = config_1.sessionAssistantSchema.parse({
|
|
96
|
-
conversationSettings: {
|
|
97
|
-
...settings,
|
|
98
|
-
...(transferOptions
|
|
99
|
-
? {
|
|
100
|
-
transferOptions: {
|
|
101
|
-
phoneNumber: transferOptions.phoneNumber,
|
|
102
|
-
message: transferOptions.message
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
: {}),
|
|
106
|
-
...(patience !== "normal" ? { patience } : {})
|
|
107
|
-
},
|
|
108
|
-
languageModel: {
|
|
109
|
-
model,
|
|
110
|
-
temperature: lm.temperature,
|
|
111
|
-
maxTokens: lm.maxTokens,
|
|
112
|
-
tools: lm.tools,
|
|
113
|
-
knowledgeBase: lm.knowledgeBase
|
|
114
|
-
},
|
|
115
|
-
...(old.testCases !== undefined ? { testCases: old.testCases } : {})
|
|
116
|
-
});
|
|
117
|
-
return { ...result, assistant };
|
|
118
|
-
}
|