@ouro.bot/cli 0.1.0-alpha.881 → 0.1.0-alpha.882
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/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.882",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Live phone calls work again on both the OpenAI SIP and Media Streams paths: every tool offered to OpenAI Realtime now has a Realtime-valid schema (a top-level oneOf in external_event_disposition had made Realtime reject the whole session setup, which dropped SIP calls and left Media Streams audio as noise), folded schemas explain their alternatives in the description, a tool that cannot be advertised is dropped with one warning per call and cannot be executed, and the bridge logs when Twilio reports a stream ended."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.881",
|
|
6
12
|
"changes": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<?xml version="1.0"?>
|
|
2
2
|
<Container version="2">
|
|
3
3
|
<Name>ouro-butler</Name>
|
|
4
|
-
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.882</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeRealtimeToolSchema = normalizeRealtimeToolSchema;
|
|
4
|
+
exports.realtimeToolParameters = realtimeToolParameters;
|
|
5
|
+
exports.realtimeToolsFromDefinitions = realtimeToolsFromDefinitions;
|
|
6
|
+
exports.createRealtimeToolReporter = createRealtimeToolReporter;
|
|
7
|
+
exports.createRealtimeToolAdvertiser = createRealtimeToolAdvertiser;
|
|
8
|
+
// OpenAI Realtime rejects the whole session (invalid_function_parameters) when any
|
|
9
|
+
// tool's parameters schema lacks top-level `type: "object"` or carries a top-level
|
|
10
|
+
// oneOf/anyOf/allOf/enum/const/not. Other providers' handling of those keywords is not relied
|
|
11
|
+
// on here, so every tool is normalized before it is advertised to a Realtime session.
|
|
12
|
+
// Argument validation still runs against the original schema when a tool call arrives.
|
|
13
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
14
|
+
const COMBINATORS = ["oneOf", "anyOf", "allOf"];
|
|
15
|
+
const FORBIDDEN_TOP_LEVEL = [...COMBINATORS, "enum", "const", "not"];
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function deepEqual(a, b) {
|
|
20
|
+
if (a === b)
|
|
21
|
+
return true;
|
|
22
|
+
if (Array.isArray(a)) {
|
|
23
|
+
return Array.isArray(b) && a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
|
|
24
|
+
}
|
|
25
|
+
if (isRecord(a) && isRecord(b)) {
|
|
26
|
+
const keys = Object.keys(a);
|
|
27
|
+
return keys.length === Object.keys(b).length && keys.every((key) => key in b && deepEqual(a[key], b[key]));
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function describeDefinition(value) {
|
|
32
|
+
if (isRecord(value) && typeof value.type === "string")
|
|
33
|
+
return value.type;
|
|
34
|
+
if (isRecord(value) && Array.isArray(value.type))
|
|
35
|
+
return value.type.join("|");
|
|
36
|
+
return JSON.stringify(value);
|
|
37
|
+
}
|
|
38
|
+
/** Resolves a branch that is a local `$ref`; returns undefined when it cannot be resolved. */
|
|
39
|
+
function resolveBranch(root, branch) {
|
|
40
|
+
let current = branch;
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
while (isRecord(current) && typeof current.$ref === "string") {
|
|
43
|
+
const ref = current.$ref;
|
|
44
|
+
if (seen.has(ref))
|
|
45
|
+
return undefined;
|
|
46
|
+
seen.add(ref);
|
|
47
|
+
const match = /^#\/(\$defs|definitions)\/([^/]+)$/.exec(ref);
|
|
48
|
+
const container = match ? root[match[1]] : undefined;
|
|
49
|
+
const name = match ? match[2].replace(/~1/g, "/").replace(/~0/g, "~") : "";
|
|
50
|
+
if (!isRecord(container) || !(name in container))
|
|
51
|
+
return undefined;
|
|
52
|
+
current = container[name];
|
|
53
|
+
}
|
|
54
|
+
return current;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Normalizes a tool schema for Realtime. Returns null when the schema cannot be expressed
|
|
58
|
+
* as an object (the caller should drop that tool).
|
|
59
|
+
*/
|
|
60
|
+
function normalizeRealtimeToolSchema(schema, onConflict = () => { }) {
|
|
61
|
+
if (schema === undefined)
|
|
62
|
+
return { parameters: { type: "object", properties: {} } };
|
|
63
|
+
if (!isRecord(schema))
|
|
64
|
+
return null;
|
|
65
|
+
if (schema.type !== undefined && schema.type !== "object")
|
|
66
|
+
return null;
|
|
67
|
+
const needsRewrite = schema.type === undefined || FORBIDDEN_TOP_LEVEL.some((key) => key in schema);
|
|
68
|
+
if (!needsRewrite)
|
|
69
|
+
return { parameters: schema };
|
|
70
|
+
// Fold the alternatives into one object whose properties are the union (all
|
|
71
|
+
// optional beyond the merged `required`), so the model can still call the tool.
|
|
72
|
+
const properties = isRecord(schema.properties) ? { ...schema.properties } : {};
|
|
73
|
+
const mergedRequired = [];
|
|
74
|
+
const alternatives = new Map();
|
|
75
|
+
const notes = [];
|
|
76
|
+
for (const key of COMBINATORS) {
|
|
77
|
+
const branches = schema[key];
|
|
78
|
+
if (!Array.isArray(branches))
|
|
79
|
+
continue;
|
|
80
|
+
const objectBranches = [];
|
|
81
|
+
for (const raw of branches) {
|
|
82
|
+
const branch = resolveBranch(schema, raw);
|
|
83
|
+
if (isRecord(raw) && typeof raw.$ref === "string" && branch === undefined)
|
|
84
|
+
return null;
|
|
85
|
+
if (isRecord(branch) && isRecord(branch.properties))
|
|
86
|
+
objectBranches.push(branch);
|
|
87
|
+
}
|
|
88
|
+
for (const branch of objectBranches) {
|
|
89
|
+
for (const [name, value] of Object.entries(branch.properties)) {
|
|
90
|
+
if (!(name in properties)) {
|
|
91
|
+
properties[name] = value;
|
|
92
|
+
}
|
|
93
|
+
else if (!deepEqual(properties[name], value)) {
|
|
94
|
+
const others = alternatives.get(name) ?? new Set();
|
|
95
|
+
if (others.size === 0)
|
|
96
|
+
onConflict(name);
|
|
97
|
+
others.add(describeDefinition(value));
|
|
98
|
+
alternatives.set(name, others);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const required = (branch) => Array.isArray(branch.required) ? branch.required.filter((n) => typeof n === "string") : [];
|
|
103
|
+
if (key === "allOf" || objectBranches.length === 1) {
|
|
104
|
+
for (const branch of objectBranches)
|
|
105
|
+
mergedRequired.push(...required(branch));
|
|
106
|
+
}
|
|
107
|
+
else if (objectBranches.length > 1) {
|
|
108
|
+
const parts = objectBranches.map((branch, i) => {
|
|
109
|
+
const names = required(branch);
|
|
110
|
+
return names.length > 0
|
|
111
|
+
? `(${i + 1}) ${names.join(", ")} (all required)`
|
|
112
|
+
: `(${i + 1}) no required fields`;
|
|
113
|
+
});
|
|
114
|
+
notes.push(`Provide ${key === "oneOf" ? "exactly one of" : "one or more of"}: ${parts.join("; ")}.`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const [name, others] of alternatives)
|
|
118
|
+
notes.push(`\`${name}\` may also be: ${[...others].join(", ")}.`);
|
|
119
|
+
const rewritten = { ...schema, type: "object", properties };
|
|
120
|
+
for (const key of FORBIDDEN_TOP_LEVEL)
|
|
121
|
+
delete rewritten[key];
|
|
122
|
+
const baseRequired = Array.isArray(schema.required) ? schema.required : [];
|
|
123
|
+
const required = [...new Set([...baseRequired, ...mergedRequired.filter((name) => name in properties)])];
|
|
124
|
+
if (required.length > 0)
|
|
125
|
+
rewritten.required = required;
|
|
126
|
+
return { parameters: rewritten, ...(notes.length > 0 ? { note: notes.join(" ") } : {}) };
|
|
127
|
+
}
|
|
128
|
+
function realtimeToolParameters(schema) {
|
|
129
|
+
return normalizeRealtimeToolSchema(schema)?.parameters ?? null;
|
|
130
|
+
}
|
|
131
|
+
function realtimeToolsFromDefinitions(definitions, reporter = createRealtimeToolReporter()) {
|
|
132
|
+
const tools = [];
|
|
133
|
+
for (const { function: fn } of definitions) {
|
|
134
|
+
const result = normalizeRealtimeToolSchema(fn.parameters, (property) => reporter.conflict(fn.name, property));
|
|
135
|
+
if (result === null) {
|
|
136
|
+
reporter.dropped(fn.name);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const description = [fn.description, result.note].filter(Boolean).join(" ");
|
|
140
|
+
tools.push({
|
|
141
|
+
type: "function",
|
|
142
|
+
name: fn.name,
|
|
143
|
+
...(description ? { description } : {}),
|
|
144
|
+
parameters: result.parameters,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return tools;
|
|
148
|
+
}
|
|
149
|
+
/** Emits warn events carrying the given context (agent, call id); each distinct finding is emitted once. */
|
|
150
|
+
function createRealtimeToolReporter(context = {}) {
|
|
151
|
+
const reported = new Set();
|
|
152
|
+
return {
|
|
153
|
+
dropped(toolName) {
|
|
154
|
+
const key = `drop:${toolName}`;
|
|
155
|
+
if (reported.has(key))
|
|
156
|
+
return;
|
|
157
|
+
reported.add(key);
|
|
158
|
+
(0, runtime_1.emitNervesEvent)({
|
|
159
|
+
level: "warn",
|
|
160
|
+
component: "senses",
|
|
161
|
+
event: "senses.voice_realtime_tool_dropped",
|
|
162
|
+
message: "tool schema is not valid for OpenAI Realtime; tool omitted from the voice session",
|
|
163
|
+
meta: { ...context, toolName },
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
conflict(toolName, property) {
|
|
167
|
+
const key = `conflict:${toolName}:${property}`;
|
|
168
|
+
if (reported.has(key))
|
|
169
|
+
return;
|
|
170
|
+
reported.add(key);
|
|
171
|
+
(0, runtime_1.emitNervesEvent)({
|
|
172
|
+
level: "warn",
|
|
173
|
+
component: "senses",
|
|
174
|
+
event: "senses.voice_realtime_tool_schema_conflict",
|
|
175
|
+
message: "tool schema branches define the same property differently; first definition kept for OpenAI Realtime",
|
|
176
|
+
meta: { ...context, toolName, property },
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Per-call advertiser: normalizes selections, reports each finding once per call, and
|
|
183
|
+
* answers which tool names were actually advertised to the model.
|
|
184
|
+
*/
|
|
185
|
+
function createRealtimeToolAdvertiser(context = {}) {
|
|
186
|
+
const reporter = createRealtimeToolReporter(context);
|
|
187
|
+
return {
|
|
188
|
+
tools(definitions) {
|
|
189
|
+
return realtimeToolsFromDefinitions(definitions, reporter);
|
|
190
|
+
},
|
|
191
|
+
isAdvertised(definitions, name) {
|
|
192
|
+
return realtimeToolsFromDefinitions(definitions, reporter).some((tool) => tool.name === name);
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -50,6 +50,7 @@ exports.twilioPhoneVoiceSessionKey = twilioPhoneVoiceSessionKey;
|
|
|
50
50
|
exports.outboundCallAnsweredPrompt = outboundCallAnsweredPrompt;
|
|
51
51
|
exports.computeOpenAIWebhookSignature = computeOpenAIWebhookSignature;
|
|
52
52
|
exports.validateOpenAIWebhookSignature = validateOpenAIWebhookSignature;
|
|
53
|
+
exports.parseToolArguments = parseToolArguments;
|
|
53
54
|
exports.computeTwilioSignature = computeTwilioSignature;
|
|
54
55
|
exports.validateTwilioSignature = validateTwilioSignature;
|
|
55
56
|
exports.twilioRecordingMediaUrl = twilioRecordingMediaUrl;
|
|
@@ -74,6 +75,7 @@ const tools_1 = require("../../repertoire/tools");
|
|
|
74
75
|
const tool_arguments_1 = require("../../repertoire/tool-arguments");
|
|
75
76
|
const config_1 = require("../../heart/config");
|
|
76
77
|
const runtime_1 = require("../../nerves/runtime");
|
|
78
|
+
const realtime_tool_schema_1 = require("./realtime-tool-schema");
|
|
77
79
|
const playback_1 = require("./playback");
|
|
78
80
|
const transcript_1 = require("./transcript");
|
|
79
81
|
const turn_1 = require("./turn");
|
|
@@ -1519,17 +1521,15 @@ async function prepareRealtimeToolContext(context) {
|
|
|
1519
1521
|
}
|
|
1520
1522
|
return selectRealtimeToolContext(context, mcpManager);
|
|
1521
1523
|
}
|
|
1522
|
-
function realtimeToolsFromSelection(selection) {
|
|
1523
|
-
return selection.ordinary.map(({ tool }) =>
|
|
1524
|
-
type: "function",
|
|
1525
|
-
name: tool.function.name,
|
|
1526
|
-
...(tool.function.description ? { description: tool.function.description } : {}),
|
|
1527
|
-
parameters: tool.function.parameters ?? { type: "object", properties: {} },
|
|
1528
|
-
}));
|
|
1524
|
+
function realtimeToolsFromSelection(selection, advertiser) {
|
|
1525
|
+
return advertiser.tools(selection.ordinary.map(({ tool }) => tool));
|
|
1529
1526
|
}
|
|
1530
|
-
function parseToolArguments(raw, name, selection) {
|
|
1527
|
+
function parseToolArguments(raw, name, selection, advertiser) {
|
|
1531
1528
|
if (typeof raw !== "string")
|
|
1532
1529
|
throw new Error("invalid tool arguments: expected a JSON object string");
|
|
1530
|
+
if (!advertiser.isAdvertised(selection.ordinary.map(({ tool }) => tool), name)) {
|
|
1531
|
+
throw new Error(`tool ${name} is not advertised in this voice session`);
|
|
1532
|
+
}
|
|
1533
1533
|
const schema = selection.ordinary.find((definition) => definition.tool.function.name === name)?.tool.function.parameters ?? {};
|
|
1534
1534
|
const validation = (0, tool_arguments_1.validateAdvertisedToolArguments)(raw, schema);
|
|
1535
1535
|
if (!validation.ok)
|
|
@@ -1673,6 +1673,11 @@ class TwilioOpenAIRealtimeMediaStreamSession {
|
|
|
1673
1673
|
pendingAudioPayloads = [];
|
|
1674
1674
|
openaiWs = null;
|
|
1675
1675
|
toolContext;
|
|
1676
|
+
advertiser;
|
|
1677
|
+
get toolAdvertiser() {
|
|
1678
|
+
this.advertiser ??= (0, realtime_tool_schema_1.createRealtimeToolAdvertiser)({ agentName: this.options.agentName, callSid: safeSegment(this.callSid) });
|
|
1679
|
+
return this.advertiser;
|
|
1680
|
+
}
|
|
1676
1681
|
friendStore;
|
|
1677
1682
|
resolvedContext;
|
|
1678
1683
|
sessionMessages = [];
|
|
@@ -1894,7 +1899,7 @@ class TwilioOpenAIRealtimeMediaStreamSession {
|
|
|
1894
1899
|
session: {
|
|
1895
1900
|
type: "realtime",
|
|
1896
1901
|
instructions: fullInstructions,
|
|
1897
|
-
tools: realtimeToolsFromSelection(fullContext.toolSelection),
|
|
1902
|
+
tools: realtimeToolsFromSelection(fullContext.toolSelection, this.toolAdvertiser),
|
|
1898
1903
|
tool_choice: "auto",
|
|
1899
1904
|
},
|
|
1900
1905
|
});
|
|
@@ -1925,7 +1930,7 @@ class TwilioOpenAIRealtimeMediaStreamSession {
|
|
|
1925
1930
|
},
|
|
1926
1931
|
output: realtimeOutputAudioConfig(realtime, { type: "audio/pcmu" }),
|
|
1927
1932
|
},
|
|
1928
|
-
tools: realtimeToolsFromSelection(toolContext.toolSelection),
|
|
1933
|
+
tools: realtimeToolsFromSelection(toolContext.toolSelection, this.toolAdvertiser),
|
|
1929
1934
|
tool_choice: "auto",
|
|
1930
1935
|
max_output_tokens: OPENAI_REALTIME_MAX_OUTPUT_TOKENS,
|
|
1931
1936
|
...(realtime.reasoningEffort ? { reasoning: { effort: realtime.reasoningEffort } } : {}),
|
|
@@ -2352,7 +2357,7 @@ class TwilioOpenAIRealtimeMediaStreamSession {
|
|
|
2352
2357
|
});
|
|
2353
2358
|
let output;
|
|
2354
2359
|
try {
|
|
2355
|
-
const args = parseToolArguments(event.arguments, name, this.toolContext.toolSelection);
|
|
2360
|
+
const args = parseToolArguments(event.arguments, name, this.toolContext.toolSelection, this.toolAdvertiser);
|
|
2356
2361
|
(0, runtime_1.emitNervesEvent)({
|
|
2357
2362
|
component: "senses",
|
|
2358
2363
|
event: "senses.voice_twilio_realtime_tool_start",
|
|
@@ -2712,6 +2717,11 @@ class OpenAISipPhoneSession {
|
|
|
2712
2717
|
autoResponsesSuppressedForAmd = false;
|
|
2713
2718
|
openaiWs = null;
|
|
2714
2719
|
toolContext;
|
|
2720
|
+
advertiser;
|
|
2721
|
+
get toolAdvertiser() {
|
|
2722
|
+
this.advertiser ??= (0, realtime_tool_schema_1.createRealtimeToolAdvertiser)({ agentName: this.options.agentName, callId: safeSegment(this.metadata.callId) });
|
|
2723
|
+
return this.advertiser;
|
|
2724
|
+
}
|
|
2715
2725
|
friendStore;
|
|
2716
2726
|
resolvedContext;
|
|
2717
2727
|
sessionMessages = [];
|
|
@@ -2879,7 +2889,7 @@ class OpenAISipPhoneSession {
|
|
|
2879
2889
|
},
|
|
2880
2890
|
output: realtimeOutputAudioConfig(realtime),
|
|
2881
2891
|
},
|
|
2882
|
-
tools: realtimeToolsFromSelection(toolContext.toolSelection),
|
|
2892
|
+
tools: realtimeToolsFromSelection(toolContext.toolSelection, this.toolAdvertiser),
|
|
2883
2893
|
tool_choice: "auto",
|
|
2884
2894
|
max_output_tokens: OPENAI_REALTIME_MAX_OUTPUT_TOKENS,
|
|
2885
2895
|
}),
|
|
@@ -2961,7 +2971,7 @@ class OpenAISipPhoneSession {
|
|
|
2961
2971
|
session: {
|
|
2962
2972
|
type: "realtime",
|
|
2963
2973
|
instructions,
|
|
2964
|
-
tools: realtimeToolsFromSelection(toolContext.toolSelection),
|
|
2974
|
+
tools: realtimeToolsFromSelection(toolContext.toolSelection, this.toolAdvertiser),
|
|
2965
2975
|
tool_choice: "auto",
|
|
2966
2976
|
...(realtime.reasoningEffort ? { reasoning: { effort: realtime.reasoningEffort } } : {}),
|
|
2967
2977
|
},
|
|
@@ -3450,7 +3460,7 @@ class OpenAISipPhoneSession {
|
|
|
3450
3460
|
});
|
|
3451
3461
|
let output;
|
|
3452
3462
|
try {
|
|
3453
|
-
const args = parseToolArguments(event.arguments, name, this.toolContext.toolSelection);
|
|
3463
|
+
const args = parseToolArguments(event.arguments, name, this.toolContext.toolSelection, this.toolAdvertiser);
|
|
3454
3464
|
(0, runtime_1.emitNervesEvent)({
|
|
3455
3465
|
component: "senses",
|
|
3456
3466
|
event: "senses.voice_openai_sip_tool_start",
|
|
@@ -5126,7 +5136,15 @@ function createTwilioPhoneBridge(options) {
|
|
|
5126
5136
|
if (routePath === `${basePath}/stream-ended`) {
|
|
5127
5137
|
// `<Connect action>` fires when the stream ends. A normal end just hangs up; a call whose
|
|
5128
5138
|
// stream was refused (or never connected) hears the failure line instead of dead air.
|
|
5129
|
-
|
|
5139
|
+
const callSid = params.CallSid?.trim() ?? "";
|
|
5140
|
+
const settledPending = pendingCalls.settle(callSid);
|
|
5141
|
+
(0, runtime_1.emitNervesEvent)({
|
|
5142
|
+
component: "senses",
|
|
5143
|
+
event: "senses.voice_twilio_stream_ended",
|
|
5144
|
+
message: "Twilio reported the call's media stream ended",
|
|
5145
|
+
meta: { agentName: options.agentName, callSid: safeSegment(callSid), settledPending },
|
|
5146
|
+
});
|
|
5147
|
+
return xmlResponse(settledPending ? streamFailureTwiml() : "<Hangup />");
|
|
5130
5148
|
}
|
|
5131
5149
|
if (routePath.startsWith(`${basePath}/outgoing/`)) {
|
|
5132
5150
|
const outgoingRest = routePath.slice(`${basePath}/outgoing/`.length);
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.882",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@ouro.bot/cli",
|
|
9
|
-
"version": "0.1.0-alpha.
|
|
9
|
+
"version": "0.1.0-alpha.882",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.78.0",
|
|
12
12
|
"@azure/identity": "^4.13.0",
|