@@ -19,4 +19,119 @@ describe("classifyEmbeddedAgentRunResultForModelFallback", () => {
|
19 | 19 | }), |
20 | 20 | ).toBeNull(); |
21 | 21 | }); |
| 22 | + |
| 23 | +it("classifies provider business-denial error payloads as fallback-worthy", () => { |
| 24 | +const result = classifyEmbeddedAgentRunResultForModelFallback({ |
| 25 | +provider: "zai", |
| 26 | +model: "glm-5.1", |
| 27 | +result: { |
| 28 | +payloads: [ |
| 29 | +{ |
| 30 | +isError: true, |
| 31 | +text: '{"success":false,"code":"CE-011","message":"当前ak因违规请求被禁止访问该模型"}', |
| 32 | +}, |
| 33 | +], |
| 34 | +meta: { |
| 35 | +durationMs: 42, |
| 36 | +}, |
| 37 | +}, |
| 38 | +}); |
| 39 | + |
| 40 | +expect(result).toEqual({ |
| 41 | +message: |
| 42 | +'zai/glm-5.1 ended with a provider error: {"success":false,"code":"CE-011","message":"当前ak因违规请求被禁止访问该模型"}', |
| 43 | +reason: "auth", |
| 44 | +code: "embedded_error_payload", |
| 45 | +rawError: '{"success":false,"code":"CE-011","message":"当前ak因违规请求被禁止访问该模型"}', |
| 46 | +}); |
| 47 | +}); |
| 48 | + |
| 49 | +it("preserves hook block results with auth-like error payload text", () => { |
| 50 | +const result = classifyEmbeddedAgentRunResultForModelFallback({ |
| 51 | +provider: "custom", |
| 52 | +model: "gpt-5.5", |
| 53 | +result: { |
| 54 | +payloads: [ |
| 55 | +{ |
| 56 | +isError: true, |
| 57 | +text: "Access denied by policy", |
| 58 | +}, |
| 59 | +], |
| 60 | +meta: { |
| 61 | +durationMs: 42, |
| 62 | +error: { |
| 63 | +kind: "hook_block", |
| 64 | +message: "Access denied by policy", |
| 65 | +}, |
| 66 | +}, |
| 67 | +}, |
| 68 | +}); |
| 69 | + |
| 70 | +expect(result).toBeNull(); |
| 71 | +}); |
| 72 | + |
| 73 | +it("uses provider-scoped failover matching for business-denial payloads", () => { |
| 74 | +const result = classifyEmbeddedAgentRunResultForModelFallback({ |
| 75 | +provider: "openrouter", |
| 76 | +model: "claude-3.5-sonnet", |
| 77 | +result: { |
| 78 | +payloads: [ |
| 79 | +{ |
| 80 | +isError: true, |
| 81 | +text: "Key limit exceeded", |
| 82 | +}, |
| 83 | +], |
| 84 | +meta: { |
| 85 | +durationMs: 42, |
| 86 | +}, |
| 87 | +}, |
| 88 | +}); |
| 89 | + |
| 90 | +expect(result).toEqual({ |
| 91 | +message: "openrouter/claude-3.5-sonnet ended with a provider error: Key limit exceeded", |
| 92 | +reason: "billing", |
| 93 | +code: "embedded_error_payload", |
| 94 | +rawError: "Key limit exceeded", |
| 95 | +}); |
| 96 | +}); |
| 97 | + |
| 98 | +it("does not retry unclassified non-GPT error payloads", () => { |
| 99 | +const result = classifyEmbeddedAgentRunResultForModelFallback({ |
| 100 | +provider: "custom", |
| 101 | +model: "llama-3.1", |
| 102 | +result: { |
| 103 | +payloads: [ |
| 104 | +{ |
| 105 | +isError: true, |
| 106 | +text: "the model produced an application-level error", |
| 107 | +}, |
| 108 | +], |
| 109 | +meta: { |
| 110 | +durationMs: 42, |
| 111 | +}, |
| 112 | +}, |
| 113 | +}); |
| 114 | + |
| 115 | +expect(result).toBeNull(); |
| 116 | +}); |
| 117 | + |
| 118 | +it("does not retry non-business transport error payloads", () => { |
| 119 | +const result = classifyEmbeddedAgentRunResultForModelFallback({ |
| 120 | +provider: "custom", |
| 121 | +model: "llama-3.1", |
| 122 | +result: { |
| 123 | +payloads: [ |
| 124 | +{ |
| 125 | +isError: true, |
| 126 | +text: "HTTP 500: internal server error", |
| 127 | +}, |
| 128 | +], |
| 129 | +meta: { |
| 130 | +durationMs: 42, |
| 131 | +}, |
| 132 | +}, |
| 133 | +}); |
| 134 | + |
| 135 | +expect(result).toBeNull(); |
| 136 | +}); |
22 | 137 | }); |