@askalf/dario 6.3.0 → 6.4.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
CHANGED
|
@@ -269,6 +269,8 @@ The tool doesn't know. The backend doesn't know. dario is the seam.
|
|
|
269
269
|
|
|
270
270
|
A ChatGPT Plus or Pro plan is served on **all three** of dario's endpoints: any client that speaks `/v1/chat/completions` or `/v1/responses` can use it (Codex CLI, the OpenAI SDKs, the Agents SDK, your scripts), and so can any client that speaks `/v1/messages` (Claude Code, the Anthropic SDKs, agent runtimes). The harness never needs to know which subscription is behind it — and the symmetry holds: Codex CLI runs on a Claude plan the same way.
|
|
271
271
|
|
|
272
|
+
An Anthropic-shape client that declares Anthropic's hosted `web_search_20260209` tool gets **real web search on the ChatGPT plan** (since 6.4): the plan's own search runs, and the client sees Anthropic's own blocks — `server_tool_use` with the query, `web_search_tool_result` listing the pages searched, the answer with `web_search_result_location` citations. `allowed_domains` and `user_location` carry over, and so does forcing the search with `tool_choice`; `blocked_domains` and `max_uses` do not.
|
|
273
|
+
|
|
272
274
|
```bash
|
|
273
275
|
dario add altman # prints an authorize URL; paste the redirect URL back
|
|
274
276
|
dario codex list
|
|
@@ -216,9 +216,33 @@ export interface ResponsesFunctionTool {
|
|
|
216
216
|
parameters: Record<string, unknown>;
|
|
217
217
|
strict?: boolean | null;
|
|
218
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* The Responses hosted web-search tool (v6.4). `filters.allowed_domains` and
|
|
221
|
+
* `user_location` are the two Anthropic web-search options it can carry;
|
|
222
|
+
* `blocked_domains` and `max_uses` have no equivalent and are dropped.
|
|
223
|
+
*/
|
|
224
|
+
export interface ResponsesWebSearchTool {
|
|
225
|
+
type: 'web_search';
|
|
226
|
+
filters?: {
|
|
227
|
+
allowed_domains?: string[];
|
|
228
|
+
};
|
|
229
|
+
user_location?: {
|
|
230
|
+
type: 'approximate';
|
|
231
|
+
city?: string;
|
|
232
|
+
region?: string;
|
|
233
|
+
country?: string;
|
|
234
|
+
timezone?: string;
|
|
235
|
+
};
|
|
236
|
+
search_context_size?: 'low' | 'medium' | 'high';
|
|
237
|
+
}
|
|
238
|
+
export type ResponsesTool = ResponsesFunctionTool | ResponsesWebSearchTool;
|
|
219
239
|
export type ResponsesToolChoice = 'auto' | 'none' | 'required' | {
|
|
220
240
|
type: 'function';
|
|
221
241
|
name: string;
|
|
242
|
+
}
|
|
243
|
+
/** Force the hosted web search (the backend accepts `web_search` here, not a function name). */
|
|
244
|
+
| {
|
|
245
|
+
type: 'web_search';
|
|
222
246
|
};
|
|
223
247
|
export interface ResponsesReasoningConfig {
|
|
224
248
|
/**
|
|
@@ -235,7 +259,9 @@ export interface ResponsesRequest {
|
|
|
235
259
|
model: string;
|
|
236
260
|
input: ResponsesInputItem[];
|
|
237
261
|
instructions?: string;
|
|
238
|
-
tools?:
|
|
262
|
+
tools?: ResponsesTool[];
|
|
263
|
+
/** `web_search_call.action.sources` — the searched URLs, which the Anthropic result block needs. */
|
|
264
|
+
include?: string[];
|
|
239
265
|
tool_choice?: ResponsesToolChoice;
|
|
240
266
|
parallel_tool_calls?: boolean;
|
|
241
267
|
reasoning?: ResponsesReasoningConfig;
|
|
@@ -292,7 +318,29 @@ export interface ResponsesReasoningItem {
|
|
|
292
318
|
encrypted_content?: string | null;
|
|
293
319
|
status?: string;
|
|
294
320
|
}
|
|
295
|
-
|
|
321
|
+
/**
|
|
322
|
+
* A hosted web-search step as the backend reports it. `action.type` is
|
|
323
|
+
* `search` (with `query` and, when `include: web_search_call.action.sources`
|
|
324
|
+
* was asked for, `sources`), `open_page` (`url`) or `find_in_page`
|
|
325
|
+
* (`url`, `pattern`) — probed 2026-09-12 on the ChatGPT backend.
|
|
326
|
+
*/
|
|
327
|
+
export interface ResponsesWebSearchCallItem {
|
|
328
|
+
type: 'web_search_call';
|
|
329
|
+
id?: string;
|
|
330
|
+
status?: string;
|
|
331
|
+
action?: {
|
|
332
|
+
type?: string;
|
|
333
|
+
query?: string;
|
|
334
|
+
queries?: string[];
|
|
335
|
+
url?: string;
|
|
336
|
+
pattern?: string;
|
|
337
|
+
sources?: Array<{
|
|
338
|
+
type?: string;
|
|
339
|
+
url?: string;
|
|
340
|
+
}>;
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
export type ResponsesOutputItem = ResponsesMessageItem | ResponsesResponseFunctionCall | ResponsesReasoningItem | ResponsesWebSearchCallItem | {
|
|
296
344
|
type: string;
|
|
297
345
|
[key: string]: unknown;
|
|
298
346
|
};
|
|
@@ -429,6 +477,15 @@ export type ResponsesAnthropicStreamEvent = {
|
|
|
429
477
|
id: string;
|
|
430
478
|
name: string;
|
|
431
479
|
input: Record<string, unknown>;
|
|
480
|
+
} | {
|
|
481
|
+
type: 'server_tool_use';
|
|
482
|
+
id: string;
|
|
483
|
+
name: 'web_search';
|
|
484
|
+
input: Record<string, unknown>;
|
|
485
|
+
} | {
|
|
486
|
+
type: 'web_search_tool_result';
|
|
487
|
+
tool_use_id: string;
|
|
488
|
+
content: AnthropicWebSearchResult[];
|
|
432
489
|
};
|
|
433
490
|
} | {
|
|
434
491
|
type: 'content_block_delta';
|
|
@@ -442,6 +499,9 @@ export type ResponsesAnthropicStreamEvent = {
|
|
|
442
499
|
} | {
|
|
443
500
|
type: 'input_json_delta';
|
|
444
501
|
partial_json: string;
|
|
502
|
+
} | {
|
|
503
|
+
type: 'citations_delta';
|
|
504
|
+
citation: AnthropicWebSearchCitation;
|
|
445
505
|
};
|
|
446
506
|
} | {
|
|
447
507
|
type: 'content_block_stop';
|
|
@@ -461,6 +521,22 @@ export type ResponsesAnthropicStreamEvent = {
|
|
|
461
521
|
} | {
|
|
462
522
|
type: 'message_stop';
|
|
463
523
|
};
|
|
524
|
+
/** One searched page, as Anthropic's `web_search_tool_result` lists them. */
|
|
525
|
+
export interface AnthropicWebSearchResult {
|
|
526
|
+
type: 'web_search_result';
|
|
527
|
+
url: string;
|
|
528
|
+
title: string;
|
|
529
|
+
encrypted_content: string;
|
|
530
|
+
page_age: string | null;
|
|
531
|
+
}
|
|
532
|
+
/** A citation on a text block, as Anthropic's `citations_delta` carries it. */
|
|
533
|
+
export interface AnthropicWebSearchCitation {
|
|
534
|
+
type: 'web_search_result_location';
|
|
535
|
+
url: string;
|
|
536
|
+
title: string;
|
|
537
|
+
cited_text: string;
|
|
538
|
+
encrypted_index: string;
|
|
539
|
+
}
|
|
464
540
|
/**
|
|
465
541
|
* One parsed Responses SSE event — a typed superset of the fields this
|
|
466
542
|
* translator consumes. Every event carries a `type`; the rest are present
|
|
@@ -480,6 +556,14 @@ export interface ResponsesStreamEvent {
|
|
|
480
556
|
summary_index?: number;
|
|
481
557
|
/** Text / argument / reasoning fragment on `*.delta` events. */
|
|
482
558
|
delta?: string;
|
|
559
|
+
/** `output_text.annotation.added`: a url_citation over the item's text. */
|
|
560
|
+
annotation?: {
|
|
561
|
+
type?: string;
|
|
562
|
+
url?: string;
|
|
563
|
+
title?: string;
|
|
564
|
+
start_index?: number;
|
|
565
|
+
end_index?: number;
|
|
566
|
+
};
|
|
483
567
|
/** error event fields. */
|
|
484
568
|
code?: string | null;
|
|
485
569
|
message?: string;
|
|
@@ -288,7 +288,7 @@ function translateAssistantBlocks(blocks) {
|
|
|
288
288
|
out.push(call);
|
|
289
289
|
return out;
|
|
290
290
|
}
|
|
291
|
-
function translateToolChoice(choice) {
|
|
291
|
+
function translateToolChoice(choice, webSearchName) {
|
|
292
292
|
if (!choice || typeof choice !== 'object')
|
|
293
293
|
return undefined;
|
|
294
294
|
switch (choice.type) {
|
|
@@ -299,7 +299,12 @@ function translateToolChoice(choice) {
|
|
|
299
299
|
case 'any':
|
|
300
300
|
return 'required';
|
|
301
301
|
case 'tool':
|
|
302
|
-
// Responses forced form is FLATTENED: {type:'function', name}.
|
|
302
|
+
// Responses forced form is FLATTENED: {type:'function', name}. Forcing
|
|
303
|
+
// the hosted web search is its own shape — `{type:'function', name:
|
|
304
|
+
// 'web_search'}` names a function the request never declared and the
|
|
305
|
+
// backend 400s ("Tool choice 'function' not found in 'tools'").
|
|
306
|
+
if (typeof choice.name === 'string' && choice.name === webSearchName)
|
|
307
|
+
return { type: 'web_search' };
|
|
303
308
|
return typeof choice.name === 'string'
|
|
304
309
|
? { type: 'function', name: choice.name }
|
|
305
310
|
: 'required';
|
|
@@ -361,11 +366,37 @@ export function anthropicToResponsesRequest(body, targetModel, options = {}) {
|
|
|
361
366
|
const instructions = flattenSystem(body.system);
|
|
362
367
|
if (instructions.length > 0)
|
|
363
368
|
out.instructions = instructions;
|
|
369
|
+
// The client's name for the hosted web search, once one is declared, so a
|
|
370
|
+
// forced tool_choice on it translates to the hosted-tool choice.
|
|
371
|
+
let webSearchName;
|
|
364
372
|
if (Array.isArray(body.tools)) {
|
|
365
373
|
const tools = [];
|
|
374
|
+
let webSearch = false;
|
|
366
375
|
for (const tool of body.tools) {
|
|
367
376
|
if (!tool || typeof tool.name !== 'string')
|
|
368
377
|
continue;
|
|
378
|
+
// Anthropic's hosted web search → the backend's hosted web search. The
|
|
379
|
+
// two options both sides speak travel; the rest is dropped. Asking for
|
|
380
|
+
// `action.sources` is what lets the result block name the pages it
|
|
381
|
+
// searched rather than come back empty.
|
|
382
|
+
const t = tool;
|
|
383
|
+
if (typeof t.type === 'string' && t.type.startsWith('web_search') && !webSearch) {
|
|
384
|
+
webSearch = true;
|
|
385
|
+
webSearchName = tool.name;
|
|
386
|
+
const ws = { type: 'web_search' };
|
|
387
|
+
if (Array.isArray(t.allowed_domains) && t.allowed_domains.length > 0)
|
|
388
|
+
ws.filters = { allowed_domains: t.allowed_domains.filter((d) => typeof d === 'string') };
|
|
389
|
+
const loc = t.user_location;
|
|
390
|
+
if (loc && typeof loc === 'object') {
|
|
391
|
+
const l = { type: 'approximate' };
|
|
392
|
+
for (const k of ['city', 'region', 'country', 'timezone'])
|
|
393
|
+
if (typeof loc[k] === 'string')
|
|
394
|
+
l[k] = loc[k];
|
|
395
|
+
ws.user_location = l;
|
|
396
|
+
}
|
|
397
|
+
tools.push(ws);
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
369
400
|
if (!tool.input_schema || typeof tool.input_schema !== 'object')
|
|
370
401
|
continue;
|
|
371
402
|
const fn = {
|
|
@@ -379,8 +410,10 @@ export function anthropicToResponsesRequest(body, targetModel, options = {}) {
|
|
|
379
410
|
}
|
|
380
411
|
if (tools.length > 0)
|
|
381
412
|
out.tools = tools;
|
|
413
|
+
if (webSearch)
|
|
414
|
+
out.include = ['web_search_call.action.sources'];
|
|
382
415
|
}
|
|
383
|
-
const toolChoice = translateToolChoice(body.tool_choice);
|
|
416
|
+
const toolChoice = translateToolChoice(body.tool_choice, webSearchName);
|
|
384
417
|
if (toolChoice !== undefined && out.tools)
|
|
385
418
|
out.tool_choice = toolChoice;
|
|
386
419
|
if (body.tool_choice?.disable_parallel_tool_use === true && out.tools) {
|
|
@@ -579,6 +612,10 @@ export function responsesStreamToAnthropicSSE(options = {}) {
|
|
|
579
612
|
const blockByOutputIndex = new Map();
|
|
580
613
|
let sawToolCall = false;
|
|
581
614
|
let syntheticToolSeq = 0;
|
|
615
|
+
/** Text streamed per message item, so a url_citation's indices can be turned into cited_text. */
|
|
616
|
+
const textByOutputIndex = new Map();
|
|
617
|
+
/** server_tool_use ids per web_search_call output index, for the result block. */
|
|
618
|
+
const searchIdByOutputIndex = new Map();
|
|
582
619
|
function ensureStarted(event, events) {
|
|
583
620
|
if (started)
|
|
584
621
|
return;
|
|
@@ -620,6 +657,46 @@ export function responsesStreamToAnthropicSSE(options = {}) {
|
|
|
620
657
|
});
|
|
621
658
|
return index;
|
|
622
659
|
}
|
|
660
|
+
/**
|
|
661
|
+
* A hosted web-search step: a `server_tool_use` block opened at
|
|
662
|
+
* output_item.added, its input (query or url) written at output_item.done
|
|
663
|
+
* when the backend reveals the action, then a `web_search_tool_result`
|
|
664
|
+
* block listing the pages the step touched — the searched sources when the
|
|
665
|
+
* request asked for them, the opened page otherwise.
|
|
666
|
+
*/
|
|
667
|
+
function openSearchBlock(item, outputIndex, events) {
|
|
668
|
+
const index = nextIndex++;
|
|
669
|
+
open = { kind: 'search', index, outputIndex };
|
|
670
|
+
blockByOutputIndex.set(outputIndex, { kind: 'search', index });
|
|
671
|
+
const id = strOr(item?.id) || `srvtoolu_responses_${syntheticToolSeq++}`;
|
|
672
|
+
searchIdByOutputIndex.set(outputIndex, id);
|
|
673
|
+
events.push({ type: 'content_block_start', index, content_block: { type: 'server_tool_use', id, name: 'web_search', input: {} } });
|
|
674
|
+
return index;
|
|
675
|
+
}
|
|
676
|
+
function finishSearchBlock(item, outputIndex, events) {
|
|
677
|
+
const ws = (item ?? {});
|
|
678
|
+
const action = ws.action ?? {};
|
|
679
|
+
const input = action.type === 'search' || action.query
|
|
680
|
+
? { query: strOr(action.query) || (Array.isArray(action.queries) ? strOr(action.queries[0]) : '') }
|
|
681
|
+
: action.url ? { url: strOr(action.url), ...(action.pattern ? { pattern: strOr(action.pattern) } : {}) } : {};
|
|
682
|
+
const blk = blockByOutputIndex.get(outputIndex);
|
|
683
|
+
if (blk && blk.kind === 'search') {
|
|
684
|
+
events.push({ type: 'content_block_delta', index: blk.index, delta: { type: 'input_json_delta', partial_json: JSON.stringify(input) } });
|
|
685
|
+
}
|
|
686
|
+
closeOpenBlock(events);
|
|
687
|
+
const results = [];
|
|
688
|
+
const seen = new Set();
|
|
689
|
+
const urls = Array.isArray(action.sources) ? action.sources.map((s) => strOr(s?.url)).filter(Boolean) : action.url ? [strOr(action.url)] : [];
|
|
690
|
+
for (const url of urls) {
|
|
691
|
+
if (seen.has(url))
|
|
692
|
+
continue;
|
|
693
|
+
seen.add(url);
|
|
694
|
+
results.push({ type: 'web_search_result', url, title: '', encrypted_content: '', page_age: null });
|
|
695
|
+
}
|
|
696
|
+
const index = nextIndex++;
|
|
697
|
+
events.push({ type: 'content_block_start', index, content_block: { type: 'web_search_tool_result', tool_use_id: searchIdByOutputIndex.get(outputIndex) ?? '', content: results } });
|
|
698
|
+
events.push({ type: 'content_block_stop', index });
|
|
699
|
+
}
|
|
623
700
|
function openThinkingBlock(outputIndex, events) {
|
|
624
701
|
const index = nextIndex++;
|
|
625
702
|
open = { kind: 'thinking', index, outputIndex };
|
|
@@ -719,12 +796,16 @@ export function responsesStreamToAnthropicSSE(options = {}) {
|
|
|
719
796
|
openToolBlock(event.item, outputIndex, events);
|
|
720
797
|
else if (itemType === 'reasoning')
|
|
721
798
|
openThinkingBlock(outputIndex, events);
|
|
799
|
+
else if (itemType === 'web_search_call')
|
|
800
|
+
openSearchBlock(event.item, outputIndex, events);
|
|
722
801
|
// message → nothing; the text block opens on the first delta.
|
|
723
802
|
break;
|
|
724
803
|
}
|
|
725
804
|
case 'response.output_text.delta': {
|
|
726
805
|
if (typeof event.delta === 'string' && event.delta.length > 0) {
|
|
727
|
-
const
|
|
806
|
+
const oi = numOr(event.output_index, 0);
|
|
807
|
+
const index = ensureTextBlock(oi, events);
|
|
808
|
+
textByOutputIndex.set(oi, (textByOutputIndex.get(oi) ?? '') + event.delta);
|
|
728
809
|
events.push({
|
|
729
810
|
type: 'content_block_delta',
|
|
730
811
|
index,
|
|
@@ -733,6 +814,24 @@ export function responsesStreamToAnthropicSSE(options = {}) {
|
|
|
733
814
|
}
|
|
734
815
|
break;
|
|
735
816
|
}
|
|
817
|
+
case 'response.output_text.annotation.added': {
|
|
818
|
+
// A url_citation over the message text → a citation on the text
|
|
819
|
+
// block, with the cited span cut from what has streamed so far.
|
|
820
|
+
const a = event.annotation;
|
|
821
|
+
if (a && a.type === 'url_citation' && typeof a.url === 'string') {
|
|
822
|
+
const oi = numOr(event.output_index, 0);
|
|
823
|
+
const index = ensureTextBlock(oi, events);
|
|
824
|
+
const text = textByOutputIndex.get(oi) ?? '';
|
|
825
|
+
const from = numOr(a.start_index, 0);
|
|
826
|
+
const to = numOr(a.end_index, from);
|
|
827
|
+
events.push({
|
|
828
|
+
type: 'content_block_delta',
|
|
829
|
+
index,
|
|
830
|
+
delta: { type: 'citations_delta', citation: { type: 'web_search_result_location', url: a.url, title: strOr(a.title), cited_text: text.slice(Math.max(0, from), Math.max(from, to)), encrypted_index: '' } },
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
736
835
|
case 'response.function_call_arguments.delta': {
|
|
737
836
|
if (typeof event.delta === 'string' && event.delta.length > 0) {
|
|
738
837
|
const index = resolveBlock(numOr(event.output_index, 0), 'tool', events);
|
|
@@ -758,6 +857,11 @@ export function responsesStreamToAnthropicSSE(options = {}) {
|
|
|
758
857
|
}
|
|
759
858
|
case 'response.output_item.done': {
|
|
760
859
|
const outputIndex = numOr(event.output_index, 0);
|
|
860
|
+
const itemType = event.item && typeof event.item === 'object' ? event.item.type : undefined;
|
|
861
|
+
if (itemType === 'web_search_call') {
|
|
862
|
+
finishSearchBlock(event.item, outputIndex, events);
|
|
863
|
+
break;
|
|
864
|
+
}
|
|
761
865
|
if (open && open.outputIndex === outputIndex)
|
|
762
866
|
closeOpenBlock(events);
|
|
763
867
|
break;
|
|
@@ -861,7 +965,7 @@ export function createAnthropicMessageAssembler() {
|
|
|
861
965
|
}
|
|
862
966
|
else if (e.type === 'content_block_start') {
|
|
863
967
|
blocks[e.index] = { ...e.content_block };
|
|
864
|
-
if (e.content_block.type === 'tool_use')
|
|
968
|
+
if (e.content_block.type === 'tool_use' || e.content_block.type === 'server_tool_use')
|
|
865
969
|
partialJson.set(e.index, '');
|
|
866
970
|
}
|
|
867
971
|
else if (e.type === 'content_block_delta') {
|
|
@@ -873,6 +977,8 @@ export function createAnthropicMessageAssembler() {
|
|
|
873
977
|
b.thinking = String(b.thinking ?? '') + d.thinking;
|
|
874
978
|
else if (d.type === 'input_json_delta')
|
|
875
979
|
partialJson.set(e.index, (partialJson.get(e.index) ?? '') + d.partial_json);
|
|
980
|
+
else if (d.type === 'citations_delta')
|
|
981
|
+
(b.citations = (Array.isArray(b.citations) ? b.citations : [])).push(d.citation);
|
|
876
982
|
}
|
|
877
983
|
else if (e.type === 'message_delta') {
|
|
878
984
|
if (msg) {
|
|
@@ -894,7 +1000,7 @@ export function createAnthropicMessageAssembler() {
|
|
|
894
1000
|
message(fallbackModel) {
|
|
895
1001
|
for (const [i, raw] of partialJson) {
|
|
896
1002
|
const b = blocks[i];
|
|
897
|
-
if (!b || b.type !== 'tool_use')
|
|
1003
|
+
if (!b || (b.type !== 'tool_use' && b.type !== 'server_tool_use'))
|
|
898
1004
|
continue;
|
|
899
1005
|
b.input = safeParseArguments(raw);
|
|
900
1006
|
}
|
package/dist/codex-backend.js
CHANGED
|
@@ -717,6 +717,9 @@ export function createResponsesTranslator(model) {
|
|
|
717
717
|
export const CODEX_SUPPORTED_FIELDS = [
|
|
718
718
|
'model', 'input', 'stream', 'store', 'instructions',
|
|
719
719
|
'tools', 'tool_choice', 'parallel_tool_calls', 'reasoning',
|
|
720
|
+
// `web_search_call.action.sources` (v6.4): accepted by the backend, probed
|
|
721
|
+
// 2026-09-12 — the searched URLs the Anthropic result block lists.
|
|
722
|
+
'include',
|
|
720
723
|
// Sent by the Codex CLI on every request (codex-rs ResponsesApiRequest), so
|
|
721
724
|
// accepted by construction; see codexPromptCacheKey.
|
|
722
725
|
'prompt_cache_key',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@askalf/dario",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.0",
|
|
4
4
|
"description": "Use your Claude and ChatGPT subscriptions in Cursor, Cline, Aider, Claude Code and the Agent SDK — at subscription pricing, not per-token API bills. One local Anthropic + OpenAI-compatible endpoint: either plan answers either wire shape, with automatic failover when one hits its limit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|