shraga 0.1.61 → 0.1.63

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.
@@ -27,11 +27,32 @@ const DIRECTIVE_KEYS = ['model', 'turns', 'thinking', 'think', 'effort', 'engine
27
27
 
28
28
  /** MODEL_ALIASES only covers the bare Anthropic shorthands. A `provider/model` id
29
29
  * (`cursor/composer-2.5`, `openai/gpt-5.6`) is already concrete — gating it on the alias table
30
- * silently dropped it and ran the instance default instead. Honoured ONLY in `model:` key form:
31
- * as a bare positional it is indistinguishable from prompt text like `[src/foo.ts]`, which a
32
- * second bracket group would then swallow. */
30
+ * silently dropped it and ran the instance default instead. As a bare positional it is honoured
31
+ * only when a REGISTERED engine actually advertises it (see the resolver below) an arbitrary
32
+ * `[src/foo.ts]` stays prompt text, which is what a second bracket group must not swallow. */
33
33
  const isQualifiedModel = (v: string) => /^[a-z0-9._-]+\/[a-z0-9./_-]+$/.test(v);
34
34
 
35
+ /** Resolves a model token the alias table doesn't know against the REGISTERED engines' own model
36
+ * lists, and reports which engine owns it. Without this, `[composer-2.5]` (an agentx model) was
37
+ * warned about and dropped, so the turn silently ran on the previous engine/model. Injected by
38
+ * `initEngines()` — directives.ts stays pure and dependency-free for CE and for tests. */
39
+ export type ModelResolver = (token: string) => { model: string; engine?: string } | null;
40
+ let modelResolver: ModelResolver | null = null;
41
+ export function setModelResolver(fn: ModelResolver | null): void { modelResolver = fn; }
42
+
43
+ /** Alias table first (canonical shorthands win), then the engine registry. */
44
+ function resolveModelToken(d: Directives, val: string): boolean {
45
+ if (MODEL_ALIASES[val]) { d.model = MODEL_ALIASES[val]; return true; }
46
+ const hit = modelResolver?.(val);
47
+ if (hit) {
48
+ d.model = hit.model;
49
+ // An engine-owned model implies its engine — but never override an explicit `[engine:x]`.
50
+ if (hit.engine && !d.engine) d.engine = hit.engine;
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+
35
56
  /** Does a bracket group look like directives (vs. prompt text that happens to start with `[`)?
36
57
  * Every token must be a known key:value or a known positional, else we leave the group alone. */
37
58
  function isDirectiveGroup(raw: string): boolean {
@@ -41,7 +62,7 @@ function isDirectiveGroup(raw: string): boolean {
41
62
  const colonIdx = t.indexOf(':');
42
63
  if (colonIdx !== -1) return DIRECTIVE_KEYS.includes(t.slice(0, colonIdx).trim().toLowerCase());
43
64
  const v = t.toLowerCase();
44
- return !!MODEL_ALIASES[v] || /^\d+$/.test(v) || ['think', 'adaptive', 'nothink', 'nothinking'].includes(v);
65
+ return !!MODEL_ALIASES[v] || !!modelResolver?.(v) || /^\d+$/.test(v) || ['think', 'adaptive', 'nothink', 'nothinking'].includes(v);
45
66
  });
46
67
  }
47
68
 
@@ -82,8 +103,8 @@ export function parseDirectives(text: string): ParsedPrompt {
82
103
  applyDirective(directives, key, val);
83
104
  } else {
84
105
  const val = t.toLowerCase();
85
- if (positionalIndex === 0 && MODEL_ALIASES[val]) {
86
- directives.model = MODEL_ALIASES[val];
106
+ if (positionalIndex === 0 && resolveModelToken(directives, val)) {
107
+ // handled
87
108
  } else if (positionalIndex <= 1 && /^\d+$/.test(val)) {
88
109
  directives.turns = parseInt(val, 10);
89
110
  } else if (['think', 'adaptive'].includes(val)) {
@@ -105,8 +126,8 @@ export function parseDirectives(text: string): ParsedPrompt {
105
126
  function applyDirective(d: Directives, key: string, val: string) {
106
127
  switch (key) {
107
128
  case 'model':
108
- if (MODEL_ALIASES[val]) d.model = MODEL_ALIASES[val];
109
- else if (isQualifiedModel(val)) d.model = val;
129
+ if (resolveModelToken(d, val)) break;
130
+ if (isQualifiedModel(val)) d.model = val;
110
131
  else console.warn(`[directives] Unknown model alias: "${val}"`);
111
132
  break;
112
133
  case 'turns':
@@ -4,6 +4,7 @@ export { ClaudeCodeEngine } from './claude-code.ts';
4
4
 
5
5
  import { registerEngine, getEngine, getAvailableEngines, hasEngine } from './registry.ts';
6
6
  import { ClaudeCodeEngine } from './claude-code.ts';
7
+ import { setModelResolver } from '../directives.ts';
7
8
 
8
9
  let _initialized = false;
9
10
 
@@ -17,6 +18,27 @@ export async function initEngines(): Promise<void> {
17
18
  // only; a directive requesting an unregistered engine falls back to claude-code (resolveAndGetEngine).
18
19
  registerEngine(new ClaudeCodeEngine());
19
20
 
21
+ // Let `[<model>]` name ANY registered engine's model (e.g. `[composer-2.5]`) and imply its engine.
22
+ // Resolved lazily on each parse so engines an add-on registers later are covered too.
23
+ // `[composer-2.5]` must reach the engine's real id (`cursor/composer-2.5`), so an exact match is
24
+ // tried first, then the bare suffix after the provider prefix — and ONLY when it is unambiguous
25
+ // across engines, so a name two engines share is never silently routed to whichever came first.
26
+ setModelResolver((token) => {
27
+ const t = token.toLowerCase();
28
+ const hits: { model: string; engine: string }[] = [];
29
+ for (const name of getAvailableEngines()) {
30
+ for (const m of getEngine(name).getModels()) {
31
+ if (!m.value) continue;
32
+ const v = m.value.toLowerCase();
33
+ if (v === t) return { model: m.value, engine: name };
34
+ if (v.slice(v.lastIndexOf('/') + 1) === t) hits.push({ model: m.value, engine: name });
35
+ }
36
+ }
37
+ if (hits.length === 1) return hits[0];
38
+ if (hits.length > 1) console.warn(`[directives] Ambiguous model "${token}" (${hits.map((h) => `${h.engine}:${h.model}`).join(', ')}) — use the full id`);
39
+ return null;
40
+ });
41
+
20
42
  console.log(`[engine] Available engines: ${getAvailableEngines().join(', ')}`);
21
43
  }
22
44